<!DOCTYPE html>













<html class="theme-next mist" lang="zh-CN">
<head><meta name="generator" content="Hexo 3.8.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">








  <meta http-equiv="Cache-Control" content="no-transform">
  <meta http-equiv="Cache-Control" content="no-siteapp">





  <meta name="msvalidate.01" content="091E32560781583695D5921C69F6EF8B">













<link rel="stylesheet" href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2">

<link rel="stylesheet" href="/css/main.css?v=7.0.1">


  <link rel="apple-touch-icon" sizes="180x180" href="/favicon.ico?v=7.0.1">


  <link rel="icon" type="image/png" sizes="32x32" href="/favicon.ico?v=7.0.1">


  <link rel="icon" type="image/png" sizes="16x16" href="/favicon.ico?v=7.0.1">


  <link rel="mask-icon" href="/favicon.ico?v=7.0.1" color="#222">







<script id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Mist',
    version: '7.0.1',
    sidebar: {"position":"left","display":"remove","offset":12,"onmobile":false,"dimmer":false},
    back2top: true,
    back2top_sidebar: false,
    fancybox: false,
    fastclick: false,
    lazyload: false,
    tabs: true,
    motion: {"enable":false,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>


  




  <meta name="description" content="作者:王越 来源:《程序员》">
<meta name="keywords" content="macOS">
<meta property="og:type" content="article">
<meta property="og:title" content="Mac OS X 背后的故事（上）">
<meta property="og:url" content="https://blog.khs1994.com/macos/story.html">
<meta property="og:site_name" content="康怀帅技术博客">
<meta property="og:description" content="作者:王越 来源:《程序员》">
<meta property="og:locale" content="zh-CN">
<meta property="og:updated_time" content="2018-12-26T02:24:17.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Mac OS X 背后的故事（上）">
<meta name="twitter:description" content="作者:王越 来源:《程序员》">






  <link rel="canonical" href="https://blog.khs1994.com/macos/story.html">



<script id="page.configurations">
  CONFIG.page = {
    sidebar: "",
  };
</script>

  <title>Mac OS X 背后的故事（上） | 康怀帅技术博客</title>
  






  <script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?f7e8103e6889b467899735d682c1b6d1";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>







  <noscript>
  <style>
  .use-motion .motion-element,
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-title { opacity: initial; }

  .use-motion .logo,
  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-CN">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta">
    

    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">康怀帅技术博客</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
    
      
        <h1 class="site-subtitle" itemprop="description">blog.khs1994.com</h1>
      
    
    
  </div>

  <div class="site-nav-toggle">
    <button aria-label="切换导航栏">
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>



<nav class="site-nav">
  
    <ul id="menu" class="menu">
      
        
        
        
          
          <li class="menu-item menu-item-home">

    
    
    
      
    

    

    <a href="/" rel="section"><i class="menu-item-icon fa fa-fw fa-home"></i> <br>首页</a>

  </li>
        
        
        
          
          <li class="menu-item menu-item-archives">

    
    
    
      
    

    

    <a href="/archives/" rel="section"><i class="menu-item-icon fa fa-fw fa-archive"></i> <br>归档</a>

  </li>
        
        
        
          
          <li class="menu-item menu-item-categories">

    
    
    
      
    

    

    <a href="/categories/" rel="section"><i class="menu-item-icon fa fa-fw fa-th"></i> <br>分类</a>

  </li>
        
        
        
          
          <li class="menu-item menu-item-tags">

    
    
    
      
    

    

    <a href="/tags/" rel="section"><i class="menu-item-icon fa fa-fw fa-tags"></i> <br>标签</a>

  </li>
        
        
        
          
          <li class="menu-item menu-item-about">

    
    
    
      
    

    

    <a href="/about/" rel="section"><i class="menu-item-icon fa fa-fw fa-user"></i> <br>关于</a>

  </li>

      
      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br>搜索</a>
        </li>
      
    </ul>
  

  
    

  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off" placeholder="搜索..." spellcheck="false" type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



  



</div>
    </header>

    
  
  
  
  

  

  <a href="https://github.com/khs1994" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewbox="0 0 250 250" style="fill: #222; color: #fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"/><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"/><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"/></svg></a>



    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  
    <div class="reading-progress-bar"></div>
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://blog.khs1994.com/macos/story.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="khs1994">
      <meta itemprop="description" content="康怀帅技术博客，包括PHP、Python、Docker、Linux、macOS、数据库、开发工具等内容。欢迎大家访问！">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="康怀帅技术博客">
    </span>

    
      <header class="post-header">

        
        
          <h2 class="post-title" itemprop="name headline">Mac OS X 背后的故事（上）

              
            
          </h2>
        

        <div class="post-meta">
          <span class="post-time">

            
            
            

            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              

              
                
              

              <time title="创建时间：2017-01-03 13:00:00" itemprop="dateCreated datePublished" datetime="2017-01-03T13:00:00+08:00">2017-01-03</time>
            

            
              

              
                
                <span class="post-meta-divider">|</span>
                

                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                
                  <span class="post-meta-item-text">更新于</span>
                
                <time title="修改时间：2018-12-26 10:24:17" itemprop="dateModified" datetime="2018-12-26T10:24:17+08:00">2018-12-26</time>
              
            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/macOS/" itemprop="url" rel="index"><span itemprop="name">macOS</span></a></span>

                
                
              
            </span>
          

          
            
            
              
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
            
                <span class="post-meta-item-text">评论数：</span>
                <a href="/macos/story.html#comments" itemprop="discussionUrl">
                  <span class="post-comments-count valine-comment-count" data-xid="/macos/story.html" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          
            <span id="/macos/story.html" class="leancloud_visitors" data-flag-title="Mac OS X 背后的故事（上）">
              <span class="post-meta-divider">|</span>
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              
                <span class="post-meta-item-text">阅读次数：</span>
              
                <span class="leancloud-visitors-count"></span>
            </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <p>作者:王越 来源:《程序员》</p>
<a id="more"></a>
<p>作者王越，美国宾夕法尼亚大学计算机系研究生，中国著名 TeX 开发者，非著名 OpenFOAM 开发者。</p>
<p>Mac OS X 背后的故事（一）力挽狂澜的Ellen Hancock</p>
<p>Mac OS X 背后的故事（二）Linus Torvalds的短视</p>
<p>Mac OS X 背后的故事（三）Mach之父Avie Tevanian</p>
<p>Mac OS X 背后的故事（四）政客的跨界</p>
<p>Mac OS X 背后的故事（五）Jean-Marie Hullot的Interface Builder神话</p>
<p>Mac OS X 背后的故事（六）上善若水</p>
<p>Mac OS X 背后的故事（七）上善若水下——Cordell Ratzlaff 引发的 Aqua 革命</p>
<p>Mac OS X 背后的故事（八）三好学生Chris Lattner的LLVM编译工具链</p>
<p>Mac OS X 背后的故事（九）半导体的丰收</p>
<p>Mac OS X背后的故事（十）Mac OS X文件系统的来龙去脉</p>
<h1 id="Mac-OS-X-背后的故事（一）力挽狂澜的-Ellen-Hancock"><a href="#Mac-OS-X-背后的故事（一）力挽狂澜的-Ellen-Hancock" class="headerlink" title="Mac OS X 背后的故事（一）力挽狂澜的 Ellen Hancock"></a>Mac OS X 背后的故事（一）力挽狂澜的 Ellen Hancock</h1><p>Ellen Hancock曾任苹果公司技术总监</p>
<p>　　故事还得从 20 世纪 90 年代说起。Ellen Hancock 是本文的主人公，也是一位女英雄。她因在 IBM 的经历而被人们所熟悉。1966-1995 年间，Ellen Hancock 在 IBM 共工作了 29 年。1985 年，她成为 IBM 的副主席。在 1986-1988 年间，Ellen Hancock 担任过 IBM 通信产品的主席，并在 1992 年被选为资深副总裁。1995 年 9 月，她被时任美国国家半导体（National Semiconductor）CEO的 Gil Amelio 忽悠，跳槽来到这个企业，做执行副总裁。她在这里带领团队完成了 CompactRISC 架构，这个架构事后成为 ARM7 系列的前身。很多人早已经把她忘了，也很少有人能够在回忆时将她和 Mac OS X 联系起来。但事实上，她是让苹果放弃 Copland 转而购买 NeXT 的关键人物。</p>
<p>早在 1994 年，Gil Amelio 就找好了下家 Apple，成为 Apple 董事会的成员。1997 年 2 月，Gil Amelio 从 National Semiconductor 辞职，并成为 Apple 的 CEO。为了紧跟老板的召唤，Ellen Hancock 再次被忽悠，来到了当时危机四伏的 Apple。这时是 1996 年 5 月，为什么是危机四伏呢？还得从早先的事情说起。</p>
<p>　　20 世纪 80 年代，卖可乐的 John Sculley 成为 Apple 的 CEO，随之 Steve Jobs 被轰出Apple。毕竟可乐和计算机不是一回事，因此不管是硬件还是 Mac OS，整个公司的开发项目越来越受阻。而且由于先天的不足，Mac OS 从诞生之初就不具有一个现代操作系统所应有的特性。所以，在 1987 年，开发下一代操作系统的计划呼之欲出。具体的规划是，把新的系统所需要的功能，写在一堆卡片上。短期可实现的目标，比如增加颜色支持（当时计算机仍是黑白的），写在蓝色的卡片上；长期的目标，比如多任务功能，写在粉色的卡片上；而在可预见的未来都无法实现的长期的目标，比如加一个纯物件导向的文件系统，就写在红色的卡片上。在这样的思路下，Mac OS 的开发团队马上就被分成两个组，一个叫蓝组，目标是在 1991 年，发布一个关于 Mac OS 的更新版本；另一个叫粉组，和蓝组同时工作，计划在 1993 年，发布一个全新的操作系统。</p>
<p>　　1991 年 5 月 13 日，蓝组顺利按时完成开发任务，发布了 Mac OS 7（一般被称为 System 7），而粉组却没做出什么有实际用途的东西来，因此接连跳票。而且，由于 Mac OS 7 的发布缺乏人手，为了保持正常发布，常常需要从粉组抽调人员参加蓝组的开发，再加上 Apple 当时把重心放在了和 IBM 等公司的合作上（Taligent 项目）而不是在粉组上，最终导致了粉组项目夭折。而本来 Apple 指望和 IBM 合作的Taligent 项目能开发出一个可用的新系统，但后来 IBM 不跟 Apple 继续玩了，因而 Taligent 的果子又吃不到，Apple 相当郁闷。这时由于 Mac OS 有先天不足（单任务，没有内存保护），再加上 Apple 以及第三方软件的无限量增加（在这段时期，单 Apple 自己就已经加入了 QuickDraw、PowerTalk、QuickTime 等软件和技术，每一个都比 Mac OS 本身来得大），Mac OS 的问题终于大爆发。上个世纪 90 年代，Mac OS 给人的印象就是很不稳定、经常崩溃，同 Windows 95 留给 PC 用户的印象差不多，甚至更甚。</p>
<p>　　Taligent 项目挂掉后，Apple 自己尝试过十多个不同的内部项目，但大多没做多久就夭折了。而这时正是 Windows NT 走向成熟的关键时期。眼看着日子逐渐变得不好过了，Apple 开始重新开始考虑建立下一代操作系统的事情。1994年，Mac OS 7.5（Mozart）发布后，Apple 推出新规划，建立一个全新的操作系统，以 Copland 命名（纪念 Aaron Copland，Mac OS 的发布以音乐家名字命名，和 Mac OS X 后使用猫科动物名字很不一样），这个项目将有一个全新的内核，具有类似 Windows NT 内核的所有高级特性，而老的软件都当作独立的进程模拟运行。这个项目时间紧、任务重，1995 年 3 月公布计划，预期 1996 年发布。而 Copland 后的版本 Gershwin（纪念 George Gershwin），预计 1997 年发布，将重写 Mac 的所有系统主要部件，以适合新内核的各种特性。</p>
<p>　　Copland 将使用微内核技术，只做任务和内存分配。除此之外的所有功能，比如文件系统、硬件驱动等作为微内核上的服务运行。而 Mac OS 的所有用户界面功能将成为一个独立的框架，称为蓝盒（Blue Box，今后介绍 Mac OS X 时，我们还会遇到这个词）。所有的任务相互独立，占用独立内存，也可以用 IPC 相互交流。学过操作系统的人都知道，微内核是当时的一个热词，一个系统只有被称为微内核才可被看作是先进的，当时还有针对 Linux 系统的著名的 Tanenbaum-Torvalds 笔战。但事实证明，所有本来想做成微内核系统的成功项目都放弃了原先的设计（包括 NeXTSTEP、Windows NT），因为这种类似 Mach 微内核的系统往往难产，GNU/Mach + Hurd 之类的项目做到现在经过了20年，仍未成事，一年内搞一个微内核系统谈何容易。</p>
<p>　　微内核还没搞成，Apple 几乎所有开发组的成员都来添乱。大家都说自己做的东西很重要，一定要加入 Copland 开发组，所以 QuickDraw GX、OpenDoc 之类的开发组产品成为新系统的核心组件，甚至类似用户界面皮肤之类的开发组都来凑热闹，马上就使 Copland 成为一个无法维护的项目。开出的计划越来越长，项目越来越多，但相关进展越来越少，完成速度越来越慢。即便做出了产品，连测试人手都不够。1995 年就有工程师指出，在 1996 年发布 Copland 纯粹是幻想，能 1997 年发布就不错了。</p>
<p>　　1996年，Gil Amelio 已经掌权。在苹果电脑全球研发者大会上他开心地宣布，传说中的 Copland，也就是 System 8 的开发版会在当年夏天发布，而正式版在秋天就可以送到每位用户手上。时任 TidBITs 编辑的 Matt Neuburg 有幸见到了这个传说中的系统。令他大吃一惊的是，这个系统在当时只能打开或关闭文件，而无法对文本文件进行编辑，甚至所有用户界面的文本框都不能输字。哪怕什么都没做，整个系统也会随机崩溃，而崩溃甚至会造成文件系统损坏。参加演示的苹果员工，则需要不断守在旁边，他们的工作是不断地格式化已崩溃的计算机磁盘，然后重装系统。那年夏天，第零个测试版送到一小簇不明真相的开发者手中，把那些脆弱的没见过世面的人吓得半死。就连 Apple 内部都开玩笑说 Copland 的正式发布日期可能得推迟到 2030 年。</p>
<p>　　Gil Amelio 心急如焚，希望 Copland 快点走到正道上来。作为 Gil Amelio 永远的好朋友，Ellen Hancock 就在这个乱糟糟的时候来到了 Apple。她的职务，正是担任这个乱糟糟项目的负责人。她亲自下访各小组体察民情，了解情况。毕竟在 IBM 干了近三十年，她依靠自己过人的判断力在 2~3 个月内便得出结论，Copland 这个项目是没有指望的，就按目前 Apple 这样的状态，Copland 永远都不可能发布，还不如早点取消了好。在短期内，先把 Copland 中的一些有用的成果一点点合并到老的 Mac OS 中，并且抓紧从外部购买一个全新系统来满足 Apple 的需要。正是她的这个结论，结束了 Apple 长达五年的纠结，使公司重新走向正轨。整个 PC 的黄金时代已经过去，Apple 想要翻身，还有很长一段路要走。Gil Amelio 支持了 Ellen Hancock 的计划。1996 年 8 月，Apple 取消 Copland 项目。开发预览版的 CD 封套都已制完，每个邮包上的地址都已打印就续，而 CD 却从未曾制出。</p>
<p>　　1996-1998 年是 Apple 最混乱的几年。在商业上，有一阵曾传出 Apple 要被 Sun 收购的消息。更有意思的是，《连线》杂志在 1997 年的六月还发表了一篇文章，名为《101 种拯救 Apple 的方法》，其中一条说最好的方式是 Apple 让自己被 Motorola 买下，成为 Motorola 的一个部门，做 PowerPC 系列产品。以当时的眼光来看这些建议非常讽刺好笑，以今天的眼光看更为好笑。而 Ellen Hancock 在这段时间内的出色工作，成功地挽救了 Apple。</p>
<p>　　首先，Ellen Hancock 的对内政策是继续 Mac OS 7.5 的开发工作，一步步把 Copland 中的技术并到 7.5 中。同时，也大量购买第三方的系统增强包，包括插件管理工具、层次化菜单等技术。Apple 把它们买过来，整合到现有的系统中。整个老系统在新系统尚未完成的时候不断更新，至 2000 年已出到 9.0 版，尽可能地留住了老用户。并且，前面提到的蓝盒（Blue Box）也作为后来新 Mac OS X 系统的一部分，支持用户运行经典 Mac OS 的程序。</p>
<p>　　而对外政策更是一个大手笔。Ellen Hancock 协助 Gil Amelio 在 Apple 之外找寻操作系统技术。在 IBM 和 Microsoft 合作 Big Blue 的经验告诉她，购买一个操作系统的使用权问题多多，最好的计划是整个一并买下来。因此，Gil Amelio 开始和当时看好的 Be 谈，却因价格问题没有成功，最终转而收购了 NeXT。而 Apple 合并 NeXT 后，NeXTSTEP 就演化为 Rhapsody，并最终成为 Mac OS X。这些事情我们今后会详细再谈。</p>
<p>　　买完 NeXT 后，Steve Jobs 执政，Gil Amelio 因任 CEO 期间 Apple 亏损严重而被炒。Steve Jobs 把信得过的人（很多是前 NeXT 员工）拉拢到周围，开始新政，而同 Gil Amelio 有关的 Ellen Hancock 则在人事变动中被疏远。Steve Jobs 甚至在很多场合称她为“笨蛋”。Ellen Hancock 最终于 1998 年主动辞职。事后同 Gil Amelio 以及 Apple 的创始人之一 Steve Wozniak 一同创业，但始终不景气，她的辉煌时代已经过去。</p>
<p>　　Gil Amelio 总结他在 Apple 时期的工作时说：“Apple 是一艘底部有洞漏水的船，而我的工作是把这船引向正道。”（Apple is like a ship with a hole in a bottom, leaking water, and my job is to get this ship pointed in the right direction. ）Ellen Hancock 虽然同 Gil Amelio 一样，不知如何去堵这个漏水的洞，但正是由于她在 Apple 的出色表现，不但把船引到了正道上，还找来了有能力堵这个洞的人。</p>
<h1 id="Mac-OS-X-背后的故事（二）——Linus-Torvalds的短视"><a href="#Mac-OS-X-背后的故事（二）——Linus-Torvalds的短视" class="headerlink" title="Mac OS X 背后的故事（二）——Linus Torvalds的短视"></a>Mac OS X 背后的故事（二）——Linus Torvalds的短视</h1><p>　　本文主要的故事来源是 Linus Torvalds 的自传《Just for Fun: The Story of an Accidental Revolutionary》。</p>
<p>Steve Jobs 于 1997 年回归 Apple</p>
<h2 id="Steve-Jobs对Mac-OS-X的考虑"><a href="#Steve-Jobs对Mac-OS-X的考虑" class="headerlink" title="Steve Jobs对Mac OS X的考虑"></a>Steve Jobs对Mac OS X的考虑</h2><p>　　1997 年，Steve Jobs 回归，开发下一代操作系统的工作被提上日程。此刻的时代背景是像 Linux 这样的开源软件大行其道。随着网络的发展，使得像 Red Hat、VA Linux 之类的企业成为爆发户，把泡沫越吹越大。Steve Jobs 承认 Linux 的好处，甚至在若干年后介绍 Mac OS X 底层的 Darwin 时还不忘在幻灯片上写道：Darwin 是类似 Linux 的系统。而当时精明的 Steve Jobs 在考虑下面几个问题。<br>　　第一，NeXTSTEP 的内核和外围工具中，BSD 代码维护起来需要大量人力，而且各分支的 BSD 发展显然不如 Linux 快。很多功能都没有，需要 Apple 自己做。</p>
<p>　　第二，像 Apple 这样的小公司，需要借力打力。Apple 的主要竞争对手是 Microsoft，而开源软件的矛头也是 Microsoft，如果联合起来干革命，不但能让自己得到一个好名声（Apple 事后一直自称是最大的开源软件公司），也可以获得可观利益，从而对 Microsoft 造成压力。</p>
<p>　　第三，也是最重要的，联合各开源组织能够推动 Mac OS 的发展。毕竟开源软件中像 GCC 之类都是很成熟的项目，Apple 用起来省时省力，投点钱就有大效益，多好。</p>
<p>　　所以，把 Linux 内核作为 Mac OS X 的重要组成部分的想法被这位伟大的智者想了出来。Apple 之前也有开发 Linux 的经验，比如在 Steve Jobs 回归之前，Apple 就和 OSF 合作开始把 Mach 内核移植到 PowerPC 上（Apple 是最大的 PowerPC 玩家，而 OSF 是最大的 Mach 玩家），并把 Linux 作为服务跑在 Mach 上。这个系统就是 MkLinux，我们在后续的连载中还会提到这个系统，因为它不但对 Linux 的移植性作出了重要的贡献，也对后来的 Mac OS X 的 XNU 内核技术起到了相当重要的作用。</p>
<p>　　如果可以采用 Linux 作为系统重要组成部分，并且这个构想能够取得在开源软件界呼风唤雨的 Linus Torvalds 的认同，就能靠他在社区鼓动一大群开发者皈依 Apple 麾下，这是 Apple 很想看到的给力结局。有了这个指导思想，他便让秘书给 Linux 的开发者 Linus Torvalds 发了一个邮件，问他是不是有一到两小时的时间和 Steve Jobs 会面。不明真相的 Linus Torvalds 收到邮件后相当高兴，因为这是他第一次有机会去硅谷观摩。</p>
<h2 id="无果而终的会面"><a href="#无果而终的会面" class="headerlink" title="无果而终的会面"></a>无果而终的会面</h2><p>　　Apple 总部 Infinity Loop 终于迎来了这位稀客，Steve Jobs 亲自接见，而先前任 NeXT 技术总监的 Avie Tevanian（这人的故事我们今后会提到）也参加了这次会谈。不用多说，这次讨论的内容自然是还处于未知状态的 Mac OS X。讨论算不上正式，但 Linus Torvalds 的愤青个性，却让谈判陷入僵局。</p>
<p>　　Steve Jobs 自然搬出他 1997 年回归之际在 MacWorld 讲话时的那套理论，Apple 虽然很颓，但骨子里是个牛逼的公司。全世界桌面领域的真正玩家就两个，一个是 Apple，另一个是 Microsoft，两者加起来，构成百分之百的桌面用户群。所以，Linus 同学，你就从了我们吧，如果你从了我们，让我们把 Mac 架在 Linux 上，一大批桌面用户就是 Linux 用户啦，前景可是一片大好！</p>
<p>　　而 Linus Torvalds 那时候牛啊，诸多大公司如 IBM、Red Hat 都围着他转。他可是企业家中的大红人，像 Apple 这样的企业根本就不在他眼里。作为一个开源软件的革命家，在他的想象中 Linux 的潜在用户应该比 Apple 还多。他始终相信，按照目前开源软件的发展态势，自己很快就能在桌面领域分到一杯羹。而且这个命题在他这种古怪性格下的直接推论是，即使我能占领桌面领域，我也要摆出一副不在乎这个领域的态度来。所以实际上 Steve Jobs 的开场白一开始就失败了。</p>
<p>　　接着，Avie Tevanian 向 Linus Torvalds 介绍了整个计划。他们想把 Mach 和 Linux 内核合并起来作为 Mac OS X 的基础，我估计 Linus Torvalds 是听错了（因为 Avie Tevanian 很早就意识到相比于微内核，混合内核有明显优势），他以为 Apple 想把 Linux 作为 Mach 的一个服务来跑（当然我个人认为，即使是合并 Mach 和 Linux 成为混合内核，依 Linus Torvalds 的愤青性格，依然是不可能接受的），这正让他回想到先前和 Tanenbaum 教授的笔战。并且，他也知道 Apple 和 IBM 合搞的失败项目 Taligent 正是用 Mach 的。</p>
<p>　　Linus Torvalds 对于微内核有他自己的看法，之前也曾在不同的地方表述过。若把关于微内核的笔战去掉限制级敏感词的话可概括成两方面。一方面，设计一个微内核和相关的服务，可能造成各种设计上的灾难。GNU/Hurd 早在八十年代末就考虑尝试在 Mach 上写一系列 Unix 的服务层，结果他们始终无法搞明白到底是让这些服务先发消息到另几个服务呢，还是考虑其他方案。所以直到 2011 年我写这篇文章时，Hurd 项目依然处于半死不活的状态。而另一方面，微内核的效率无法和传统内核相比，最简单的系统调用会涉及一系列底层服务的互相通信。所以很多研究者着手研究如何把微内核的效率提上去，结果就导致微内核变得更加复杂。能提高微内核效率的很多研究成果都已在 Mach 项目中实现了。而在 Linus Torvalds 看来这恰使 Mach 成为了一个非常复杂的项目，并且效率也不怎么高。</p>
<p>　　会谈时坐一旁的 Avie Tevanian 事实上是 Mach 最早的开发者之一，他热情地给 Linus 讲述 Mac OS X 系统蓝图。而 Linus 实际上早就不耐烦了。比如，Mac OS X 中，有一个模拟层，可让用户使用经典的 Mac OS 程序。这个技术极类似于现在跑在 Unix 系统上执行 Windows 程序的 Wine 。Apple 当时的考虑是这样，因为老的 Mac OS 在设计 API 时，就没有考虑到类似内存保护之类的问题，所以这层 API 必须废掉，Mac OS X 中所有的新程序必须采用 NeXT 的那套更先进的 API（根据我的考证，当时还没有 Carbon 这样的想法，而且事实上 Carbon 不管在 API 还是 ABI 上都和经典 Mac OS 不兼容）。而短期内已有的软件又不可能快速重写迁移至 Mac OS X。所以，如果用户需要使用老版 Mac OS 的第三方应用程序，就可以使用 Apple 提供的这个兼容层。但是由于刚才提到的原因，老版程序并不享受新版程序的待遇，因为模拟器本身运行多个老 Mac OS 任务时，和原先老版 Mac OS 一样，实际上只有一个进程，没有内存保护。这样做的好处是明显的，因为一方面老的程序在 Mac OS X 发布之初还能用，另一方面 Apple 又和老技术划清了界限，逼着开发者使用新技术，技术方面的原因是最重要的。但这个看似很正确的技术在 Linus Torvalds 看来是古怪的，他想当然地认为，完全可以运行多个不同的模拟器进程，来执行不同的任务，使得每个任务都可以享受内存保护。这种浪漫主义情调让他无比鄙视 Apple 员工的智商。而事后当笔者使用早期版本的 Mac OS X 时，发现 Linus Torvalds 的想法完全是不切实际的。因为这个模拟层本来就要占用不少的内存和 CPU，在处理器速度不及今日手机、内存无比精贵的 90 年代末，跑一堆模拟器进程无异于是和自己过不去。</p>
<p>　　Steve Jobs 考虑到 Linus Torvalds 是开源软件的领军人物，便继续以开源为话题，动之以情，晓之以理。他告诉 Linus Torvalds，我们这个系统做出来后呢，所有的 Unix 层（非图形界面层），都会开源，所以事实上你加入我们，也是在给开源做贡献啊！而由于在开源圈子混久了，Linus Torvalds 对此丝亳不领情，他认为，有谁会想用一个底层是开源而图形界面是不开源的系统呢？所以，像笔者这样的用户被“代表”了。</p>
<h2 id="Mac-OS-X-与-Linux-分道扬镳"><a href="#Mac-OS-X-与-Linux-分道扬镳" class="headerlink" title="Mac OS X 与 Linux 分道扬镳"></a>Mac OS X 与 Linux 分道扬镳</h2><p>　　总之，这次会面完全谈崩，两人站在不同的角度去看问题，加上 Steve Jobs 和 Linus Torvalds 都是个性鲜明、唯我独尊的人，技术和商业上的考虑都不同，所以会谈中双方简直就是鸡同鸭讲。这次讨论也使得 Apple 放弃 Linux，转而采用 FreeBSD 技术，并在 2001 年任命 FreeBSD 的发起者、领军人物 Jordan Hubbard 为 BSD 技术小组的经理，并在后来升为 Unix 技术总监。至于 Apple 的内核技术后来走向何方，我们下期再讲。</p>
<p>　　笔者认为，Apple 和 Linus Torvarlds 的商谈破裂，以今天的眼光来看，是因 Linus Torvarlds 的自命清高和短视造成的。他不懂得尊重其他开发者的意见，并且不断抬扛。包括后来关于 C++ 的论战。Mac OS X 发布后，Linus Torvalds 又数次嘲笑 Mac 的技术落后，并说这些他在当年和 Steve Jobs 开会时就预料到了。直到最近，他终于有些成熟，对 Mac OS X 的观点开始缓合，但还是不忘批评 Mac 的文件系统就是垃圾（事实上，Linux 的也没好到哪去，至少 Apple 还搞过一阵 ZFS）。这种性格最终导致在 Mac OS X 和 iOS 大行其道的时候，Linus Torvalds 连兔子汤都不曾分到。<br>而事实上这对 Apple 也是件好事。Apple 重要的是利益而不是折腾，即使是开源也是利益驱动。像类似 Linux 开发组那样自以为是但代码又写得差的开源项目，Apple 事后也遇到不少，比如 GCC 编译器项目组。虽然大把钞票扔进去，在先期能够解决一些问题，但时间长了这群人总和 Apple 过不去，并以自己在开源世界的地位恫吓之，最终 Apple 由于受不了这些项目组人员的态度、协议、代码质量，觉得还不如自己造轮子来得方便，因此 Apple 推动了类似 LLVM 这样宏伟的项目，并且在短短几年内，使其成为最领先的开源软件技术。这无异于扇了 Linux 小组、GCC 小组一记响亮的耳光。</p>
<h1 id="Mac-OS-X-背后的故事（三）Mach-之父-Avie-Tevanian"><a href="#Mac-OS-X-背后的故事（三）Mach-之父-Avie-Tevanian" class="headerlink" title="Mac OS X 背后的故事（三）Mach 之父 Avie Tevanian"></a>Mac OS X 背后的故事（三）Mach 之父 Avie Tevanian</h1><p>　　1975 年，美国罗彻斯特大学纽约分校，一组研究员正在做一个名为 RIG（Rochester’s Intelligent Gateway）的项目，它由 Jerry Feldman 主持设计。RIG 的目标是给所有本地以及远端的计算设备（比如磁盘、列印机、磁带、绘图机等）提供一组统一的访问方式，其作业系统称为 Aleph。为了实现所需要的功能，Aleph 的内核主要构建了一个进程交互（Interprocess Communication，IPC）的机制。RIG 的各进程，只要设置了目标端口，就可以彼此间发送信息。RIG 项目没过几年就被判了死刑，主要是缺少很多有用的功能，比如端口没有保护机制，一次最多只能发送 2KB 大小的信息（受硬件限制），也没有很好的网络支持等。不过在 20 世纪 70 年代，这个系统依然代表着当时作业系统设计的先进水平，比如除了进程交互外，每个进程还有内存保护的功能，这足以让 20 世纪 90 年代末都没有做出内存保护技术的 Apple 公司汗颜。</p>
<p>　　该项目后来失败了，随后在 1979 年，RIG 的 Richard Rashid 博士毕业到卡内基-梅隆大学当教授，开始做 Accent 项目。它是一个网络作业系统，于 1981 年 4 月开始活跃开发。受 RIG 的影响，Accent 系统的亮点也在于可以使用 IPC，而且解决了很多 RIG 的不足。比如每个进程有 4GB 的虚拟内存空间，而且甚至连内核自已都可以被存入缓存页面，内存有先进的更新前拷贝（Copy-on-Write）功能，可以实现进程间大信息的传送等。读者可以把 Accent 理解为支持虚拟内存技术，并且具有网络透明 IPC 功能的 RIG 内核。</p>
<p>　　但过了几年，开发者们越来越对 Accent 失去兴趣。在 1980 年初，很多人觉得多核计算是计算机未来发展的潮流，但 Accent 内核在设计时并没有考虑到这些问题。而且，随着许多实验室纷纷购置性能更强劲的计算机，这就意味着 Accent 需要移植到新的目标架构上。此外，Unix 正大行其道，不管是在作业系统理论上还是在用户程序上，都成为最为流行的作业系统模式，而 Accent 并不是一个 Unix 系统，所以无法享受 Unix 世界的诸多美好。为了解决这个问题，研究人员决定把所有设计推翻重来，于是就有了一个全新的系统。</p>
<p>　　在匹兹堡的一个雨天，卡内基-梅隆大学的 Avie Tevanian，此系统的最主要开发者，正打着伞和同学们在去吃午饭的路上。他们一边绕着无数的泥塘，一边构思给这个新系统取什么名字好。灵感突来， Avadis Tevanian 建议把这个系统叫作 Muck，引得同学们哈哈大笑。后来，Richard Rashid 和一位意大利同事 Dario Giuse 说起这玩笑，结果这位同事不经意地把 Muck 发为 Mach，遂把 Richard Rashid 笑翻，伟大的 Mach 系统因此得名。</p>
<p>　　Mach 是一个受 Accent 启发而搞出的Unix兼容系统。那年，Unix 已经十六岁，而且依然是作业系统理论与实践开发的主要阵地。Unix 内核由于新加入的功能越来越多，变得越来越复杂。而 Mach 的一个主要目标就是尽量缩减 Unix 的各项服务，以使内核变得简单可维护。此项目从 1984 年开始，目标主要是包含完整的多任务支援、良好的硬件移植性，并要把大量服务移出内核作为跑在内核上的服务，以及提供与 Unix 的兼容性。</p>
<p>　　Mach 使用纯 C 编写，所以在一定程度上保证了可移植性，这事实上为后面的 NeXT 向 PowerPC 移植以及 2005 年的向 Intel 移植提供了很重要的前提。而为了缩减内核该管的任务，Mach 做得很绝，只提供内存和处理器管理。类似于档案系统、网络、输入输出等功能都作为单个的系统进程，独立执行于内核之上。Mach 的开发过程以 4.3 BSD 作为起点，以 RIG 的 Accent 作为参考，采纳 DEC 的虚拟内存设计思路，逐步开发，以新写的代码代替 BSD 的代码。两年后的 1986 年，虽然没能把系统服务完全分离于内核之外，但已颇见成效。Mach 第一版大功告成，组员发表会议论文，成为操作系统史上里程碑式的经典，引发操作系统业界的“微内核”学潮，如今学习作业系统设计的皆需学习此文，二十五年来被引用一千二百余次。</p>
<p>　　这篇文章主要讲了两方面内容：IPC 和虚拟内存。在 IPC 方面，Mach 把复杂的消息传送机制分为四个独立的清晰概念—任务、线程、端口、信息。任务是拥有一组系统资源的对象，允许线程在其中执行；线程是执行的基本单位，拥有一个任务的上下文，并且共享任务中的资源。</p>
<p>　　由于该论文的影响力，所以项目得到了 OSF（Open Software Foundation）在内的很多投资。当然了，学术和工程永远存在差距，所以即使是最受欢迎的 Mach 2.5 其实仍然是一个包括大多数 BSD 服务层的单内核。但包括 NeXTSTEP、OSF/1 在内的很多操作系统都采用 Mach 作为其内核技术，原因是广大研究人员依然相信微内核代表着未来。虽然 Mach 2.5 的效率比传统的 Unix 系统稍低一些，但研究者们表示情绪淡定，因为 Mach 支持多处理器系统，可以利用多线程把任务处理得飞快，相比之下其他 Unix 内核并没有多处理器的完善支援，因此 Mach 效率稍低完全可以接受。但随着真正把 Mach 和 BSD 服务完全脱离的 Mach 3 微内核面世，研究人员们的情绪就再也淡定不起来了。因为服务和内核分离后，任务间的 IPC 数量暴涨，一个简单的 Unix 系统调用要涉及到十多个开端口、设权限、发送、收取消息的操作，哪怕是使用数年后的 1997 年的硬件，跑一个系统调用密集的程序，Mach 的效率要比一般的 Unix 系统慢 50%，而且根本没有什么好方法来解决这个问题。</p>
<p>　　所以 Mach 3 出来后，虽有少数微内核信徒继续执著地改进 Mach，或者开始其他微内核比如 L4 的研究。但学术界对 Mach 的兴趣大减，因而 Mach 3 也成为最后一版。项目解散后，Richard Rashid 去了微软研究院。</p>
<p>　　再说我们的主角 Avie Tevanian，他 1987 年博士毕业去了 NeXT。这家公司刚刚由 Steve Jobs 成立两年，这两年 Steve Jobs 啥正经事都没干，只是花了十万美元雇 Paul Rand 设计了一个公司商标。直到 Avie Tevanian 加入后，这个公司才开始干实事。1987 年公司确认要开发一个面向研究人员使用的计算机工作站，于是软硬件的开发工作紧锣密鼓地展开。硬件组由领导过 Apple Lisa 的 Rich Page 原班人马负责，而软件则由 Avie Tevanian 负责，计划开发一个有图形界面的操作系统 NeXTSTEP。由于 Avie Tevanian 是 Mach 主要的开发者，自然 NeXTSTEP 就基于 Mach 了。1988 年 10 月 12 日，NeXT 发布预览版（0.8版），并于 1989 年 9 月 18 日发布 1.0 版（注：<a href="http://en.wikipedia.org/wiki/NeXTSTEP" target="_blank" rel="noopener">http://en.wikipedia.org/wiki/NeXTSTEP</a>  ）。</p>
<p>　　作为 NeXTSTEP 系统的内核，NeXT 分支的 Mach 经历了不少变化。NeXTSTEP 0.8 主要使用 Mach 2.0 版，而稍后的 NeXTSTEP 1.0 版主要基于 Mach 2.5 版，包含一个自己定制的当时最新的 4.3 BSD 服务层。从 3.1 版开始，NeXT 分支的 Mach 还包括一个全新的设备驱动框架， 名为 Driver Kit，仅供 x86 系列的硬件使用。和 Mach 以及 BSD 代码不同，Driver Kit 是使用 Objective-C 写的。为什么是一个面向对象的语言呢？看 NeXTSTEP 3.3 的 DriverKit 文档。读者大概就会发现，NeXTSTEP 把所有硬件设备理解为对象，而我们知道，对象之间有继承关系，比如，磁盘（IODisk物件）属于输入输出设备（IODevice物件）的子物件，而磁盘（IODisk）本身又是逻辑磁盘（IOLogicalDisk）的父物件。硬件的初始化对应于每个物件的初始化（init方法），硬件又有读、写，所以可以用 getter/setter 的方法。因此，DriverKit 是一个非常有特色的实现。而且由于 Objective-C 的效率很高，依赖很少（Objective-C 程序可以直接被编译器翻译成等价的C语言程序并编译，而 Objective-C 的运行库 libobjc 也以高效著称），所以也是编写驱动的良好选择。几年后的 IOKit 其实就是个 DriverKit 的翻版。</p>
<p>　　这时，NeXTSTEP 操作系统大获成功，风险投资商们纷纷购买，但硬件却始终卖不出去（注：Aaron Hillegass《Cocoa Programming for Mac OS X》前言），所以 NeXT 砍掉了硬件部门专做软件，更是使 NeXTSTEP 发展到了巅峰时期，同时支持 68K、x86、PA-RISC 和 SPARC 等硬件，但颇有意味的是它就是不支持 PowerPC 架构。它可以同时产生一个包含所有架构可执行码的二进制文件，来使开发的程序在所有平台上执行。这个功能也影响了后来 Mac OS X 的技术。Mac OS X 10.4 时代有两件跨时代意义的事情，一件是 Apple 搞出了 64 位的 Power Mac，开发者可以发布一个包含64位和32位程序的单一可执行文件，而无需让用户去区分；另一件是和 Intel 合作。Apple 正式发表了 Universal Binary 技术，可以一个 Mach-O 文件同时包含 Intel 和 PowerPC 的指令。这非常贴心的设计（要知道，大多数电脑用户根本不知道 Intel、PowerPC、64位、32位等技术）就是来自于 Mach 的技术。</p>
<p>　　NeXTSTEP 3.3 后，NeXTSTEP 因为 NeXT 和 Sun 的合作改名为 OPENSTEP，1996 年发布 4.0 版，到 1997 年 2 月 4 日，NeXT 被 Apple 收购之前，期间内核改进除源码同步到 Mach 3.0 版外不明，而且出于不知道的原因，我手头的 OPENSTEP 正式版光盘中，居然找不到 DriverKit 的发布说明和编程文档，故不作详述。不过这段时间，Apple 的活动值得好好一说。之前在《Linus Torvalds的短视》中，我们曾提到，1996 年，Apple 和 OSF 曾经合作，把 Mach 移到 PowerPC Mac 上，再把 Linux 作为单一的服务跑在 Mach 上，这个项目叫做 MkLinux。在 1996 年发布基于 Mach 3.0 和 Linux 1.3 的预览版，并更新到 2002 年结束其历史使命，对 Mach 在 PowerPC 的移植性上做出了重要贡献。这个 PowerPC 版的 Mach 被叫作 osfmk 分支，也正是现在 Mac OS X 中用的分支。当然了，NeXT 被合并后做了大量修改。</p>
<p>　　Apple 收购 NeXT 后，Mach 被确定作为未来的操作系统核心。Avie Tevanian 被选为软件开发部的总裁。合并所有项目的号角吹响后，上层的 OpenStep API 和老版 Mac OS 的部件开始合并，而 Mach 也经历重大变化。主要是一方面，Mach 使用了 osfmk 分支，但依然包含 4.3 BSD 服务；另一方面，DriverKit 被 IOKit 取代。这是 Apple 走得很被动的一步。因为当时外界普遍对 Objective-C 不看好，逼着 Apple 走老版 Mac OS  API 的老路。而 Apple 自己对 Objective-C 也很不自信，甚至想索性换用 Java 了事（我们以后会谈及这段不自信的历史）。所以 IOKit 是一个 C++ 的驱动架构，来符合大众口味。这些改变最早在 Rhapsody 中出现（我们以后也会有一期 Rhapsody 的专题）。但由于 C++ 是门很恐怖的语言，所以 Apple 又把 C++ 给阉割了，去掉了多重继承、模板、运行时动态以及异常，让开发者使用这种对于 Objective-C 来说换汤不换药的 Clean C++ 来做驱动。但公正地说，IOKit 对于 Driver Kit 是有不少改进的，比如 IOKit 可以写在用户空间跑的驱动（虽然大多仍是跑在内核空间上的），因而驱动挂了而系统不会挂。另外 IOKit 考虑到了计算机发展的趋势，所以在电源管理、即插即用、动态加载上做得更好。</p>
<p>　　但各位也知道，C++ 程序得用专门的运行库才能跑，所以 Mach 中又加入了一个叫作 libkern 的库负责 C++ 相关的功能，同时，还有一个 libsa 的库提供一些类似二分查找、排序等基本算法之类的功能。最后和硬件相关的还有一个叫作 pexpert（Platform Expert）的库，负责收集硬件设备列表、检测机器种类（比如处理器速度等）、解析启动参数等杂活。</p>
<p>　　至此，Mac OS X 的内核完全形成，形成 BSD、IOKit、Mach osfmk 三足鼎立的态势，并有 pexpert、libkern、libsa 作为基础。Apple 称它的内核杰作为 XNU。其代码开源，请读者移步<a href="http://www.opensource.apple.com/source/xnu/xnu-123.5/" target="_blank" rel="noopener">http://www.opensource.apple.com/source/xnu/xnu-123.5/</a> ，每个部分的代码都独立存放在一个文件夹中，条理清晰，不妨一读。</p>
<p>　　由于 4.3 BSD 已是过眼烟云，Apple 后来投入大量资源扶持 FreeBSD 开发。2001 年，Apple 将 FreeBSD 的发起者、领军人物 Jordan Hubbard 收入麾下，并在 Mac OS X 10.3 时基本同步到 FreeBSD 5 的代码（注：<a href="http://osxbook.com/book/bonus/ancient/whatismacosx/arch_xnu.html" target="_blank" rel="noopener">http://osxbook.com/book/bonus/ancient/whatismacosx/arch_xnu.html</a> ）。</p>
<p>　　另外，Apple 的开发也同时反馈到 FreeBSD 小组，包括 FreeBSD 6.2 内核引入的 AUDIT (man audit 或参见<a href="http://manpages.unixforum.co.uk/man-pages/unix/freebsd-6.2/4/audit-man-page.html" target="_blank" rel="noopener">http://manpages.unixforum.co.uk/man-pages/unix/freebsd-6.2/4/audit-man-page.html</a> )，后来 FreeBSD 8引入的 libdispatch （<a href="http://wiki.freebsd.org/GCD" target="_blank" rel="noopener">http://wiki.freebsd.org/GCD</a> , 在 Apple 这项技术叫 Grand Central Dispatch，是 Mac OS X 10.6 主推的新功能，FreeBSD 基本在 Mac OS X 10.6 上市的同时就拥有这项最新技术），以及 FreeBSD-CURRENT 中的 LLVM-Clang，全是 Apple 的手笔。从 1999 年开始，FreeBSD 源码仓库可以搜索到 Apple 提供的大量的补丁以及新功能。<br>　　Mac OS X 早期版本不太稳定，所以会内核崩溃。10.0 版本会直接像 Linux 或者 BSD 那样打出回溯信息，很不美观，所以 Apple 在 10.2 版本开始设计了一个多国语言的图片告诉用户你的内核崩溃了，以让内核崩得看起来更优雅一点。由于包含四国语言，被国内用户戏称为“四国”（注：优雅的图片见下图，详见 （<a href="http://support.apple.com/kb/ht1392" target="_blank" rel="noopener">http://support.apple.com/kb/ht1392</a> ），这是 XNU 的 Mach osfmk 部分的功能。但从 10.3~10.4 版本开始，系统越发稳定，正常使用已很少见到内核崩溃。而且，内核提供的服务也越来越多，使得 Mac OS X 成为一个完善的系统。</p>
<p>　　21 世纪 XNU 架构方面的最重大改动是支持了 PPC64（10.4 版本时代）、x86 架构（其实本来也一直支持的，以后讲 Apple 的 Intel 迁移时详谈）、x86_64（64位支持是苹果长年努力逐步展开的。10.4 时代 32 位内核支持载入 64 位的用户程序，10.5 系统提供 64 位的Cocoa框架，但系统大部分程序都是 32 位的，10.6 时代内核支持以 64 位模式启动，但在不少硬件上这是非默认的方式，但系统大量程序已被改写并编译为 64 位的二进制程序，10.7 时代内核默认以 64 位模式启动。）和 ARM 架构（iPhone 和 iPad 使用 XNU 内核）等多个新架构。</p>
<p>　　而其中 ARM 架构的支持别具意义。但 2006 年 5 月 31 日，功成名就的 Avie Tevanian 离开 Apple 另谋发展，此时，离 Apple 的 iPhone 奇迹发生，只有不到一年时间。</p>
<h1 id="Mac-OS-X-背后的故事（四）——-政客的跨界"><a href="#Mac-OS-X-背后的故事（四）——-政客的跨界" class="headerlink" title="Mac OS X 背后的故事（四）—— 政客的跨界"></a>Mac OS X 背后的故事（四）—— 政客的跨界</h1><p>　　2000 年，美国总统大选，由于选票设计问题，时任美国副总统的 Al Gore 败北。2000 年 12 月 13 日，在一番重新计票的大折腾不起作用后，曾经意气风发的 Al Gore 拖着疲惫的身子，走上讲台，发表了认输讲话（参见 Al Gore《2000 Presidential Concession Speech》），从此退出政坛。一般国家领导人的退政生活其往往松愉快，出出日记，学用哲学，或者像多才多艺的李岚清不但去各地推广古典音乐，更是玩起了篆刻（参见《南方周末》2006 年 5 月 11 日《老常委的卸任生活》），克林顿先生都成立个基金会来帮助社会预防和治疗爱滋。 Al Gore 也没闲着，他找到了让他感兴趣的去处——Apple 总部，并成为董事之一。</p>
<h2 id="Mac-OS-X-和-Al-Gore-的双赢"><a href="#Mac-OS-X-和-Al-Gore-的双赢" class="headerlink" title="Mac OS X 和 Al Gore 的双赢"></a>Mac OS X 和 Al Gore 的双赢</h2><p>　　2003 年 5 月 19 日，Apple 的启示中罕见性地登出了《前总统 Al Gore 加入 Apple 董事会》的快讯。文中提到，Al Gore 总统是一个正宗果粉，他一直用 Mac 计算机，而且还会用 Final Cut Pro 来编辑他的视频。Al Gore 也不掩饰他对 Apple 技术的热爱，他表示对 Mac OS X 的开发极感兴趣，并且也对 Apple 在开放源代码运动中的贡献喜闻乐见。他虚心地说，想在这个让 Apple 起死回生的董事会好好观摩并学习。</p>
<p>Al Gore 的加盟让 Apple 一跃成为电子产品的代言人</p>
<p>　　苹果公司的 CEO Steve Jobs 表示 Al Gore 曾经管理过世界最大的组织——美国政府，期间显示出的经验和智慧对苹果公司是笔巨大的财富。Al Gore 将成为出色的董事会主席，苹果将以他把苹果公司作为职业生涯的开始为荣。</p>
<p>　　这之后，Al Gore 在 Apple 内部的决策究竟起了什么作用，和 Mac OS X 的开发有何关联，在正式的渠道很少有史料，但是他后来的各种公开活动，却给 Mac OS X 的技术做足了广告，而且很多证据表明，他正是使 Apple 从被绿色人士攻击的众矢之的的状态，成为业界注重电子产品环保领头羊的主要推手。</p>
<p>　　Al Gore 重新进入普通人的视野是在 2006 年，他推出了自己参与制作和演出的纪录片《An Inconvenient Truth》（《难以忽视的真相》）和同名书籍。这部长达 94 分钟的影片，在西方国家引起了广大的回响，以 Al Gore 的一场演讲和人生的回忆作为两条主线，详细、科普地向民众介绍了全球变暖问题的科学证据及美国政府掩盖问题的真相。该片以发人深省的立意、详尽的科学数据、平实的讲演风格，加上苹果高超的技术，而获得了广泛的好评并一举获得年度奥斯卡最佳纪录片奖，使得这位美国前副总统摇身一变，成为好莱坞明星。</p>
<p>　　为什么单单一场简单的讲话，就能做出一部电影，还能得到奥斯卡这样学院艺术奖的亲睐？是因为讲话内容无懈可击么？事后有很多科学家站出来表示，虽然影片内容有积极的意义，但其实也有很多被夸大的科学数据、假设和结论。试思索，该片之所以成功，甚至成为诸多演讲培训机构的重要分析案例，除了数据、观点、论述外，还有以下几个原因。</p>
<p>　　首先，这场演讲由苹果主导的技术和艺术的设计。Al Gore 向来以说不清想表达的内容而著称。他经常因为讲得过于专业或者缺乏好的表述方法以致于民众完全不懂他在讲些什么。他的早期讲话用现在的眼光看就是个少将体，比如“互联网…网…我…这个…那个…那个…怎么说呢…我想这个…这…这…这…我啊…我啊…就是说…互联网是我发明的！”因此作为苹果展现公司软实力的重要机会，苹果非常重视这场讲话，请公司的图形设计小组带领完成各种所需设计，苹果甚至特地请来了专业的设计公司 Duarte 来进行讲稿和讲话内容的安排。因此，不管是内容安排、图形设计还是技术支持，Al Gore 都有强有力的后盾，他们能够帮助Al Gore 完成任何想达到的目标。不论是 FinalCut 还是 Keynote，一旦缺少任何 Al Gore 想要的功能，Apple 都可以给他开小灶实现。在片末的走马灯字幕中，有大量 Apple 的 Keynote 组、Final Cut 组和图形设计组的员工名字，以示鸣谢。</p>
<p>　　其次，上面这些资源的相互合作，也使得 Al Gore 的这场讲话的讲稿被精心制作，体现了精心设计的电子稿演讲所能达到的最高成就。苹果公司向来重视演讲，也是各大企业中最会通过演讲来营销产品的公司。每年的 MacWorld 和 WWDC 的 Steve Jobs 讲话都会吸引百万人在计算机前观看。每场讲话都好戏连连，台下的观众的欢呼和掌声不亚于著名歌星的演唱会。这种风格显然给 Al Gore 的讲话风格带来很大的影响。在影片中，观众看不到一个传统的 bulletpoint（PowerPoint 用户常爱使用的表示讲话结构的方法），取而代之的是高清的照片、视频，来展现环境的严峻性。观众不再会为枯燥无味的技术词语而搞得昏昏欲睡，因为屏幕上的一切都是如此真实，各种科学现象由动画效果配合，使其浅显易懂。另外，所有的数据、图表都精心使用软件制作，使其一目了然，表现准确而美观大方，而且 Al Gore 时而还会玩些小噱头，比如讲到现在的温室气体浓度是多么高时，他甚至爬上工作人员为他准备的升降机，升到舞台顶端，来告诉观众，数据已经突破图表的顶端了。现在距笔者观赏完这部影片，已经五年过去了，但影片中的灾难场景、冰川融化的影片段落、海平面上升的计算机模拟、二氧化碳浓度的数据图表，至今都记得一清二楚，足以见得其表现力是何等深入人心。甚至有人在调侃他在 2000 年的竞选演说是怎么回事？难道就是缺少了这些科技元素？</p>
<p>　　最后，Mac OS X 的各项技术也是这部片子的重要保证。Duarte 公司的 Ted Boda 表示（该幻灯片的设计师之一），Mac OS X 系统本身的反锯齿功能把文字、图片、矢量图标表现得栩栩如生，使得幻灯片充满美感。QuickTime 技术作为 Mac OS X 的一块重要基石，又使得 Keynote 不需任何插件就能引入任何图片和影像，所以类似使用 Illustrator、Photoshop、AfterEffects 等软件做出的图片、影像或动昼，不需要任何转换过程就能直接拖到 Keynote 中。哪怕 1920×1080 的高清视频，都可以轻松插入，流畅播放。他们组根本想象不出在 Windows上 使用 PowerPoint 会成什么样子。</p>
<p>　　可以说，没有 Mac OS X，就没有这部电影。而实际上这部电影的作用远胜过任何一部 Apple 公司的广告。片中 Al Gore 时时拿着 PowerBook 的笔记本，在办公室用 Safari 查网页，字体渲染真实而美观，甚至在车上都不忘打开笔记本用 Keynote 做几张幻灯片，就更不用说电影中 Keynote 幻灯片曾经迷倒多少 Windows 用户了。向笔者推荐这部电影的好朋友了解到这些全是 Apple 技术的功劳时，拥有一台 Mac 就成为其人生梦想。</p>
<h2 id="环保卫士的-Apple-之路"><a href="#环保卫士的-Apple-之路" class="headerlink" title="环保卫士的 Apple 之路"></a>环保卫士的 Apple 之路</h2><p>　　作为环保人士，Al Gore 对 Apple 的策略的影响也不容忽视。Apple 向来被各环保组织长期批评，即使 Apple 长年不断地改进这方面问题，但绿色人士依然不买帐。哪怕在稍后的 2007 年，也仍有包括 GreenPeace 在内的七十多个组织联名写信给 Al Gore，敦促 Apple 更重视环境问题，信中指责 Apple 仍在大量使用 PVC 和 BFRs 等对环境有害的材料，也不注重对自家产品的回收。由于 Al Gore 是 Apple 董事会成员，使得这个问题受到了 Apple 的广泛关注。Apple 在 2007 年后史无前例地迈开大步，大力推广环保计划（要求全世界的 IT 制造商们逐步弃用 PVC 等有毒的化学用品进行生产），让 Apple 一跃成为注重电子产品环境保护问题的领头羊。</p>
<p>　　从制造材料上，2007 年 8 月发布的 iMac 成为分水岭。这款产品的设计主要使用可完全被回收的玻璃屏和铝外框，减小了塑料等不环保物质的使用，此后苹果一发不可收拾，把这项革命进行到底，从手机到笔记本，都全番设计。2008 年的 MacBook Air 引出的 Unibody 技术是这场革命的代表产品，不但在外观上还是工程上做到极致，在环保上更是让各绿色组织无可挑剔。</p>
<p>　　在造势上，Apple 现在每项主要产品的都有“环境”的标签页，从制造、运输、耗电、回收等性能情况分产品详细列出。Apple 甚至在包装上都动足脑筋，尽量减少每个产品的包装，使得同一架飞机可以运输更多的产品，从而在运输相同数量产品的情况下减少飞机温室气体的总排放量。</p>
<p>　　Mac OS X 的各项节电功能的开发更是不用说了。休眠、调整空闲时的屏幕亮度、硬盘转速等常规功能自然越做越好。而系统的多项技术能使程序更优地分配使用中央处理器和显示卡。甚至系统还能在用户打字时，每两键之间的空隙减少处理器的占用从而节省击键之间的功耗，这使得 Mac OS X 不但更节约能源，笔记本的电池使用时间也不断提高。而这一切的变化，和 Al Gore 似乎都有着千丝万缕的联系。</p>
<p>　　由于《An Inconvenient Truth》中的讲话让 Al Gore 的观点深入人心，同时也对美国政府在京都议定的决策产生重大的压力，挪威诺贝尔委员会决定把 2007 年的诺贝尔和平奖颁给了 Al Gore，以表彰其在全球环境问题方面的努力，同时苹果的主页上全版刊发新闻，以示祝贺。贺词如下：</p>
<p>　　Al has put his heart and soul, and much of his life during the past several years, into alerting and educating us all on the climate crisis. We are bursting with pride for Al and this historic recognition of his global contributions. (Al Gore 在过去几年殚心积虑，全身心地投入对公众关于气候危机的警示和教育中。我们为他这次所得的荣誉和他全球性贡献的历史性承认感到无比自豪。）</p>
<p>　　或许，由于 Al Gore 在计算机领域的一贯低调（他也是 Google 的高级顾问），他在这些企业的工作很少被报道出来，但是他在政界的跨界身份是显而易见的。Al Gore 在他的人生道路将何去何从，我们不得而知，但是从各种媒体信息的披露可以看出，Al Gore 对计算机事业的热衷，对环保问题的投入，可能是美国历任领导人中最突出的。</p>
<h1 id="Mac-OS-X-背后的故事（五）Jean-Marie-Hullot-的-Interface-Builder-神话"><a href="#Mac-OS-X-背后的故事（五）Jean-Marie-Hullot-的-Interface-Builder-神话" class="headerlink" title="Mac OS X 背后的故事（五）Jean - Marie Hullot 的 Interface Builder 神话"></a>Mac OS X 背后的故事（五）Jean - Marie Hullot 的 Interface Builder 神话</h1><p>　　Interface Builder，是用于苹果公司 Mac OS X 操作系统的软件开发程序，Xcode 套件的一部分，于 1988 年创立。它的创造者 Jean-Marie Hullot 自称是“一个热爱旅行、充满激情的摄影师”，本篇分享 Hullot 热爱技术的那一面——创造 Interface Builder 的过程。<br>因势而动</p>
<p>　　1981年， Jean-Marie Hullot 拿到巴黎第十一大学的计算机科学博士资格后，开始了法国国家信息与自动化研究所（INRIA）的研究生活。</p>
<p>　　Jean-Marie Hullot 的名字似乎不为大众所熟知，但他设计的 Interface Builder 却深入人心，创造了一个个软件神话。<br>　　20 世纪 70 年代初，正是面向对象程序设计开始走上历史舞台的时期。许多现代计算机技术的诞生地 Xerox PARC（施乐帕洛阿尔托研究中心）的 Alan Kay、Dan Ingalls、Ted Kaehler 、Adele Goldberg 等人，从 1969 年开始研发一款面向对象的程序语言 Smalltalk，并于 1980 年正式公布。这是一个完整地实现面向对象范型的编程套件，包含了一种面向对象的程序设计语言、一种程序设计库和一个应用开发环境（ADE）。</p>
<p>　　虽然当时的机器跑得巨慢无比，但 Smalltalk 先进的思想对其他众多的程序设计语言（Objective-C、Actor、Java 和 Ruby）的产生起到了极大的推动作用，对计算机工业界的发展产生了非常深远的影响。我们将会在今后介绍 Objective-C 时，详细介绍 Smalltalk 及其对 Objective-C 的影响，这里先一笔带过。</p>
<p>　　Smalltalk 的发布在业界一石激起千层浪，也给 Jean-Marie Hullot 幼小的心灵带来了巨大的震撼。他立即明白了面向对象思想所代表的先进生产力，一定会改变今后数十年的程序设计流程，他毫不犹豫地成为面向对象编程模式的早期粉丝。</p>
<h2 id="SOS-的助力"><a href="#SOS-的助力" class="headerlink" title="SOS 的助力"></a>SOS 的助力</h2><p>　　那时，Jean-Marie Hullot 使用早期的 Macintosh 计算机进行开发。不过他很快就和其他开发者一样，发现虽然 Mac 的用户界面做得不错，但开发程序实在是太糟糕了。他说：“当 Macintosh 被发明出来时，计算机和先前就大不一样了，你至少需要花 60%~70% 的时间在用户界面部分的代码上。”在 Macintosh 被发明之前，用户界面是相当简单的，只需要在命令行下面打一串字符，计算机就会回应出一行行的信息。所以在那个时代，开发者完全不需要专注于用户界面。而 Mac 一经发布，随之而来的众多的窗口和菜单，让整个世界都不一样了。虽然对于使用最终产品的用户而言是简单方便的，但对于码工来说简直是个噩梦。每次他们需要一个窗口或者菜单，都要从零开始构建。</p>
<p>　　聪明的 Hullot 开始动脑筋改进 Mac 编写用户程序难的现状。他开发了一个程序，有点像现在 Windows 系统中的“画板”。一侧的工具条，是类似菜单这样的大量可重用的对象；而另一侧，则是程序员想构建的用户程序界面。只要把工具条上的工具拖放到程序界面中，那么类似“打开”、“打印”等相关的功能，就可以被添加到用户界面中。事实上，这个程序，是最早的一批能通过鼠标把控件拖入界面设计窗口实现相应功能的商业程序，是用户界面设计软件的先驱。</p>
<p>　　这个跨时代的发明被称作 SOS，用 Lisp 语言编写【注：What are we going to called this thing 中认为此时就是 Interface Builder，但据 The NeXTonian 等多处资料表明，在 Steve Jobs 见到以前，该程序名为 SOS】。当时，ExperTelligence 开发了一种叫做 ExperLisp 的方言，SOS 即用此语言写成【注：<a href="http://en.wikipedia.org/wiki/Interface_Builder" target="_blank" rel="noopener">http://en.wikipedia.org/wiki/Interface_Builder</a> 】。</p>
<p>　　此时 Hullot 忽然意识到，他设计的东西事实上很强大，其重要性简直可以和 Smalltalk 这样的发明相比——Smalltalk 让开发者尝到了面向对象语言的甜头，而 SOS 则是直接把对象放到了开发者手边。有了这么拽的东西，Hullot 意识到如果他只在研究所窝着，那只能让十几个人享受这一成果，而如果他跳槽，把这个工具公开，那对天下的码工来说可是大福音。</p>
<p>诞生之源</p>
<p>　　经过不断努力，Hullot 找到了一个值得推销自己发明的好地方——剑桥的苹果大学联盟（Apple University Consortium）。这个苹果和大学合作的组织看到 Hullot 的创作后反响很好，就推荐他去见 Jean-Louis Gassee。 Jean-Louis Gassee 是个法国人，时任苹果开发研究院主任，见到 SOS 后也认为这是个好东西，便说服他去美国闯一闯。经过几次的鼓励和推荐，加上美国对 Hullot 来说又不陌生，于是他就买了机票跳上飞机就奔赴美国。</p>
<p>　　不过当 Jean-Marie Hullot 来到美国加州苹果总部时，他却认为这不是一个工作的好地方——苹果已经是一个很庞大的企业，很难再有所创新发展。他最终决定不留在那儿，转而在美国寻找一个能把这个产品卖出去的人。四处推销之后，找到他用来写 SOS 的 Lisp 解释器的生产商，就是刚才提到的位于 Santa Barbara 的软件公司 ExperTelligence。</p>
<p>　　事实上，当时的 ExperTelligence 正在寻找合作商卖自已的 Lisp，而 Hullot 也在找合作商卖自已的 SOS，两者一拍即合，随即打电话给 NeXT，共同推销自家的产品。</p>
<p>　　NeXT 在 Palo Alto 总部的产品市场部人员接待了 Jean-Marie Hullot 和两位来自 ExperTelligence 的员工，被 SOS 的理念镇住，遂打电话请 Steve Jobs 下来看。Jean-Marie Hullot 像复读机一样又把自己的大作秀了一遍。老谋深算的 Steve Jobs 事实上早就看中了 SOS，但他对 ExperTelligence 的 Lisp 一点兴趣都没有。所以他装作对这场演示毫无兴致【注：这有很多引用该文的翻译译错，原文说 nonplussed，字面意思为惊异，但在美国非正式表述中，此字表毫无兴致】，挥挥手就把这三个人打发走了。</p>
<p>　　但当他们一行人走到停车场时，Steve Jobs 让他手下把 Hullot 追了回来，当他只身回到 NeXT 总部时，发现 Steve Jobs 正恭敬地等着他。</p>
<p>　　“我想要你计算机上那个程序”【注：<a href="http://rixstep.com/2/0/people/" target="_blank" rel="noopener">http://rixstep.com/2/0/people/</a> 】，Steve Jobs 说道：“你大概什么时候能开始给我们工作？”</p>
<p>　　Hullot 回答说自己翌日就要离开去度假。</p>
<p>　　“好吧，我两周后给你打电话，”Steve Jobs 说。</p>
<p>　　“不行，老乔”，Hullot 表示：“我不游美国，我可要环游欧洲，你七个礼拜后再打给我吧。”</p>
<p>　　Steve Jobs 虽然一骨子傲气，但他明白一个简单的道理：21世纪最缺的是什么——是人才！即使 Jean-Marie Hullot 玩起了大牌，这电话自然还是要打的。Hullot 刚一度完假回来，Steve Jobs 的电话就如期而至。</p>
<p>　　如此三顾茅庐般的热情，把 Jean-Marie Hullot 感动得第二天就登上了去美国的飞机。合约签了半年，但实际上他最终在 NeXT 整整待了十年。在 NeXT 工作期间，他使用 Objective-C 和 NeXTSTEP 框架重写了 SOS，命名为 Interface Builder。由此，Interface Builder 成为 NeXT 集成开发环境 Project Builder 标准套件之一。</p>
<h2 id="进阶与探索"><a href="#进阶与探索" class="headerlink" title="进阶与探索"></a>进阶与探索</h2><p>　　Interface Builder 和 SOS 一样，提供了一个工具箱，包含一系列用户控件对象。工具箱并不是官方定死的，而是可以任意扩展的，比如如果用户想使用类似 Safari 中的 toolbar，而这不是官方提供的，则下载第三方的 PSMTabBar 即可实现，甚至连 Cappuccino 这样的网页框架也可以用 Interface Builder 来完成设计。开发者只要把控件比如菜单和文本框拖入项目文件就能完成用户界面设计，节省了几乎所有和控件放置有关的代码。</p>
<p>　　开发者拖拽鼠标，将控件可提供的动作（IBAction）和另一个对象的接口（IBOutlet）连在一起， 则建立了一个绑定。这样，一旦动作被激发（比如用户点了按钮），那接口中相应的方法则会被执行。所以，大量对象关联的代码也能被省去。</p>
<p>　　有了这样的模式后，Interface Builder 和 Cocoa 可以比后来出现的 Microsoft Visual Studio 或 Qt Designer 等软件走得更远——只要是对象，Interface Builder 就能够操控它们，不需要一定是一个界面的控件。比如，数据库的数据源、队列等，都可以在 Interface Builder 中连接起来，于是很多原本需要上千行的复杂应用（比如用来显示、修改企业中职工姓名、部门、电话、地址、头像等信息 SQL 数据库的用户界面程序），数分钟内就可以写完，不用一行代码。不信？让 1992 年的 Steve Jobs 亲自做给你看【注：<a href="http://www.youtube.com/watch?v=j02b8Fuz73A" target="_blank" rel="noopener">http://www.youtube.com/watch?v=j02b8Fuz73A</a> ， 第 23 分钟～第 29 分钟】。</p>
<p>　　NeXT 被 Apple 收购后，苹果把下一代操作系统建立在 NeXTSTEP 的基础上。Objective-C 和 Cocoa 被作为主要框架，而 Interface Builder 和 Project Builder 也因此受到重用。就官方的工具箱而言，支持 Objective-C/Cocoa、Carbon 的 HIToolbox 和 WebObject。</p>
<p>　　2008 年 3 月 27 日，苹果发布首个 iPhone SDK，设计 Cocoa Touch 界面的，也正是 Interface Builder。可以说，Interface Builder 一直随着公司产品的发展而不断拓新。</p>
<p>　　Jean-Marie Hullot 是在 NeXT 被收购时进入苹果的。Steve Jobs 令他率领在法国的一个小团队，秘密为 Mac OS X 10.2 开发一个办公软件。以往这样量级的程序，都是由苹果加州总部的大班人马完成。而这次，为了向世人表明他的 Interface Builder 有多强大，iCal 横空出世，展示复杂的界面元素（日历、可拖拽的任务、五花八门的分类）和诸多功能（网络同步、Apple Script 脚本控制）可以用相当快速的时间内开发出来【注：<a href="http://www.appleinsider.com/articles/07/10/17/road_to_mac_os_x_leopard_ical_3_0.html&amp;page=2" target="_blank" rel="noopener">http://www.appleinsider.com/articles/07/10/17/road_to_mac_os_x_leopard_ical_3_0.html&amp;page=2</a> 】。</p>
<p>　　最后，在 iCal 小组打完酱油的 Jean-Marie Hullot 荣升苹果软件开发部首席技术官。</p>
<p>　　Project Builder 在 Mac OS X 10.3 时被重命名为现在大家所熟知的 Xcode。Xcode 3 以前，Interface Builder 使用一种名为 nib 格式的二进制文件格式。不过由于 nib 不能用肉眼读，也不方便使用版本管理工具来管理，所以 Xcode 3 开始新加入一种名为 xib 的文本文件格式，最后再在项目编译阶段输出为 nib 格式。和产生静态界面布局代码的工具（如 MSVC、QtDesigner、 Delphi 等类似的软件）很不同，nib 是不被转译成相应 Objective-C 代码的。用户程序执行时，nib 文件被读入，解包，并且唤醒【注：awake，即载入 nib 会自动调用程序中 awakeFromNib 方法】，所以 nib 文件是在运行时动态加载的。</p>
<p>　　长期以来，Xcode 环境和 Interface Builder 是两个独立但相互工作的程序。而 2010 年释出的 Xcode 4 预览版中，Xcode 和 Interface Builder 合二为一，成为一个一体化的编程环境。所以现在，开发者甚至可以只用鼠标在用户界面和代码间来回拖拽就能完成，这样一来 Interface Builder 对用户代码的解释也比先前更正确。比早期分离的程序使用起来确实方便很多。</p>
<p>　　当然，一个负面的影响是，这样用一体化集成开发环境写程序，往往会发现屏幕空间是不够的，所以像我这样用 11 寸 Air 或者 13 寸 Macbook Pro 的人，出去打招呼都不好意思说自己是做 Mac 开发的。<br>下一个海阔天空</p>
<p>　　在而后的岁月里，Interface Builder 创造了一个又一个应用软件神话，小到官方教程中的汇率计算器，大到苹果所有的家用、专业软件，都由 Interface Builder 完成。</p>
<p>　　在风起云涌的 1989 年，欧洲核子研究组织（CERN）工作的科学家 Emilio Pagiola 忽悠经费，买来研究所的第一台 NeXT 计算机——当时 NeXT 计算机在 CERN 可是个新鲜事物——那里的科学家们纷纷前来把玩，普通青年发现里面有全本的韦氏词典，并可自动检查用户输入的拼写错误，技术青年发现它跑的是 Unix 系统，还有一个可读写的光驱，文艺青年更是发现里面居然预装了莎翁全集。不过毕竟像 Emilio Pagiola 这样忽悠巨款买 NeXT 机器的青年不多，所以大家围观完了，也就回去该干嘛干嘛了。</p>
<p>　　但 Tim Berners-Lee 和别人不一样，他不仅围观了那台计算机，还看到了 Jean-Marie Hullot 设计的 Interface Builder，研究了 Objective-C，发现了面向对象编程范式开发环境的最高成就。这情景让他心中漾起了巨大的波澜，最终化为激情澎湃的投入，汇成了一行行面向对象的代码，一泻千里，奔向未来。</p>
<p>　　一年后，世界首个 HTTP 服务在 CERN 的 NeXT 计算机运行起来，而使用 Objective-C 和 Interface Builder 所编写的超文本语言编辑器兼浏览器同步发行。他给这个主从式架构起了个好听的名字——World Wide Web（万维网）。</p>
<h1 id="Mac-OS-X-背后的故事（六）上善若水"><a href="#Mac-OS-X-背后的故事（六）上善若水" class="headerlink" title="Mac OS X 背后的故事（六）上善若水"></a>Mac OS X 背后的故事（六）上善若水</h1><p>　　Aqua 是 Mac OS X Public Beta 全新用户界面的名字，英文中为水的词根，寓意以水为灵感，精心设计。Steve Jobs 曾介绍说，Aqua 的设计是如此之美好，初次见它甚至有想亲吻的冲动。本篇 Cordell Ratzlaff 引发的 Aqua 革命（上）介绍的是 Aqua 的起源和来历，在下篇中，我们将展示 Aqua 的具体设计过程。</p>
<p>　　“Mac OS 的图形界面就是你们那么业余的人设计的吗？” Steve Jobs 开门见山地问。</p>
<p>　　包括 Cordell Ratzlaff 在内的设计师们怯怯地点头称是。“你们就是一群白痴！” Steve Jobs 骂道。</p>
<p>　　这个场景发生在 Steve Jobs 回归不久的图形界面组组会上，前文提到的骂人的话，是他送给图形界面设计组的见面礼。【注：参见 <a href="http://www.cultofmac.com/how-mac-os-x-came-to-be-exclusive-10th-anniversary-story/87889" target="_blank" rel="noopener">http://www.cultofmac.com/how-mac-os-x-came-to-be-exclusive-10th-anniversary-story/87889</a> ，How Mac OS X Came To Be，Leander Kahney】</p>
<h2 id="不进则退的局面"><a href="#不进则退的局面" class="headerlink" title="不进则退的局面"></a>不进则退的局面</h2><p>　　Mac OS 曾是图形界面设计的先驱。</p>
<p>　　从 System 1 开始，Mac 就打破了字符终端的模式，使用图形界面和用户交互设计。但自 System 1 到 System 7，10年过去了，界面却始终没有显著的变化。设计组一直认为，为尊重用户的习惯，定下的规矩不要轻易改动。但同时，Microsoft 的变化可以说是天翻地覆，从黑屏的 DOS，到全屏幕的 Windows 1，再到成熟的 Windows 3，最后演变到奠定当今 Windows 界面基础的炫丽多彩的 Windows 95。用当时的眼光来看，这个变化是相当惊人的。由于因循守旧，Mac OS 在界面设计上从领先掉到了最后。旧的界面原语，一成不变的界面风格，让 Mac OS 的图形界面在 Windows 前显得黯然无光。【注：参见 <a href="http://vimeo.com/21742166" target="_blank" rel="noopener">http://vimeo.com/21742166</a> 】</p>
<p>　　于是，在图形界面组的组会上，Steve Jobs 抨击了老 Mac OS 界面的各种不是——几乎所有的地方都被骂了一遍。众矢之的是各种打开窗口和文件夹的方式。在 Mac OS 中有至少 8 种打开窗口和访问文件夹的方式，如弹出菜单、下拉菜单、DragStrip、Launcher、Finder 等不同的程序。</p>
<p>　　Cordell Ratzlaff 作为主管，他一开始担心是不是会被 Steve Jobs 炒掉（传闻说 Steve Jobs 刚进入苹果时最爱炒人，经常会发生一些“神奇”的情况，比如有员工和他一同进了电梯，等一同出电梯时，该员工已被炒掉）。不过批评大会进行到第 20 分钟时，Cordell Ratzlaff 转为淡定，因为他意识到如果 Steve Jobs 要炒他，不用废那么多话，早就可以动手了。</p>
<p>　　其实 Cardell Ratzlaff 是 Apple 内部较早意识到小组设计不思进取的人之一。他意识到苹果有三个重要的设计问题【注：参见 Designing Interactions 第二章 My PC 附录访谈】。第一、Apple 的很多界面语言不明确。例如，在老 Mac OS 中，删除文件的动作是把文件图标拖到废纸篓里，但当磁盘和光盘弹出时，居然也是把图标拖到废纸篓里。第二、老 Mac OS 不会对问题进行变通，如果有几个图标同时显示，窗口还容易操作，但如果有几十个图标或窗口，以相同的方式显示出来，那么在繁杂的页面中找寻所需内容，对使用者则是巨大的挑战。第三、Mac OS 的界面过于古板，看上去还是停留在 Windows 3.0 阶段。总之，当时的 Mac OS 已经不能代表先进的生产力，也不能代表科技的前进方向，更不能让广大用户得到更多的利益。在 Cardell Ratzlaff 看来， Mac OS 的界面面临不进则退的重大困局，非改不可。</p>
<h2 id="Cordell-Ratzlaff-的试水"><a href="#Cordell-Ratzlaff-的试水" class="headerlink" title="Cordell Ratzlaff 的试水"></a>Cordell Ratzlaff 的试水</h2><p>　　收购 NeXT 以后，Apple 开始考虑如何把 NeXTSTEP 作业系统变为下一代的 Apple 操作系统，但界面设计组的倦怠又浮出水面。设计组认为，这是一个浩大的工程，所以他们决定照着 Mac OS 8 的样子改 NeXTSTEP 的代码，把 NeXTSTEP 改成 System 8 的样子。这并不困难，组里只需一个人就能完成这项任务，这人的工作极其无聊——像小孩子描红模，把新界面的样子临摹得和老界面一模一样。事实上，当 Apple 释出 Rhapsody 和 Mac OS X Server 初版时，经典 Mac OS 的界面已经被学得惟妙惟肖了。</p>
<p>　　Cordell Ratzlaff 认为这种混搭，是一个极其让苹果丢颜面的事情。所以，除了那个搞山寨的人以外，他召集其他人做新界面设计的图样。而由于 NeXTSTEP 具有强大的图形处理和动画能力，因此很多新的图样是在新系统上完成的。</p>
<p>　　Apple 将“What’s not a computer!”（看起来不是电脑的电脑）的概念应用在硬件外观上，设计出具有浪漫主义气质，半透明“果冻” 式且具有艺术美感的 iMac，这成了 Aqua 设计灵感的来源。<br>　　20世纪 90 年代初，Apple 和 Microsoft 的操作系统都素面朝天，色调简单，统一的矩形窗口。到 1997~1998 年，Apple 的硬件外观设计取得重大进展：由后来成为金牌设计师的 Jonathan Ive 领衔，设计出具有浪漫主义气质、五彩斑澜的、半透明外壳、具有曲线美感的 iMac，这个设计成为 Cordell Ratzlaff 和他的同事们设计的灵感，他们马上就作出了一个全新的界面图样来。【注：参见 <a href="http://en.wikipedia.org/wiki/IMac_G3" target="_blank" rel="noopener">http://en.wikipedia.org/wiki/IMac_G3</a> 】</p>
<p>　　与此同时，Cordell Ratzlaff 着手解决前文提到的三个设计问题。第一、他提出了一个叫“实时状态”的概念。当用户拖动文件时，废纸保持原样，而如果拖动的是磁盘，那废纸篓的图标变成“弹出”的图标。第二、窗口的问题统一采用动画加以解决。比如窗口的最小化和还原都配有动画，告诉用户窗口的来去方向。当 Dock 项目有所增减时，项目长度和元素也会随之改变。第三、Mac OS 一改死板面孔，呈现多彩的、小清新的图形界面，所有尖锐的直角都被打磨成圆弧，并且有像 iMac 外壳一样半透明的菜单。当时有评论指责 Apple 的设计太卡通缺乏权威感，其变化之大可见一斑。【注：参见 <a href="http://www.aresluna.org/attached/files/usability/papers/onethousandsquarepixelsofcanvas.pdf" target="_blank" rel="noopener">http://www.aresluna.org/attached/files/usability/papers/onethousandsquarepixelsofcanvas.pdf</a> ，One thousand square pixels of canvas On evolution of icons in graphical interfaces by Marcin Wichary 第五页】</p>
<p>　　Cocoa 之父 Bertrand Serlet，作为 Cordell Ratzlaff 的上司，对新界面很满意。但当时，他们认为这个新界面实现起来难度很大，既没有时间也没有资源把这个想法在 Mac OS X 中付诸实现。于是先前那位孤独的照葫芦画瓢的设计者只好继续工作。</p>
<p>　　Aqua 只是个设想（PS 出来的图样＋模拟出来的视频），还不是能用的代码。</p>
<h2 id="Steve-Jobs-的怒火和-Aqua-的源头"><a href="#Steve-Jobs-的怒火和-Aqua-的源头" class="headerlink" title="Steve Jobs 的怒火和 Aqua 的源头"></a>Steve Jobs 的怒火和 Aqua 的源头</h2><p>　　几个月以后，Apple 举办了一个所有开发小组参加的长达两天的汇报大会。Cordell Ratzlaff 汇报的时间被排在两天的最后压轴出场。大多数工程师对这长达两天的大会报告早已疲倦，感叹 Mac OS X 剩下的的工作很艰巨，认为发布遥遥无期。于是，Cordell Ratzlaff 报告成了整个报告会的最大笑场，所有工程师使出咆哮体来评价这个工作——“啊！！！你看这新界面多出位啊！！！有没有有没有！！！居然用的透明通道！！！还搞个实时的动画！！！你难道不知道你这些永远是天方夜谭不可能完成吗？？？我们工程师伤不起啊伤不起！！！”这个新设计就这样在所有 Apple 顶级工程师的鄙视下被废了。</p>
<p>　　无奈于此，只好无聊地让那位开发者继续复制全套经典 Mac OS 界面，而当 Steve Jobs 召集所有设计组负责人时，这个山寨版 Mac OS 的展示把 Steve Jobs 看得情绪激动，就发生了文章开头的那一幕。</p>
<p>　　Cordell Ratzlaff 前来解释压轴报告的尴尬局面，暗示千里马常有而伯乐不常有的处境，还让 Steve Jobs 观摩了他的杰作。果然 Steve Jobs 看了这几张图例后大为惊异，拍着 Cordell Ratzlaff 的肩说：“很好！很强大！”然后让设计组不惜一切代价做成试验品。</p>
<p>　　在加班奋战的三周后，设计组用 Macromedia Director 完成了一个试验品。Steve Jobs 亲自来 Cordell Ratzlaff 办公室视察了一下午。结果是他激动地握着 Cordell Ratzlaff 的手，吐露心声：“你是苹果里我见到的第一个智商是三位数字的人。”得到了 Steve Jobs 的支持，Apple 的 Mac OS X 开发团队，更加紧密地围绕在以 Cordell Ratzlaff 为核心的界面设计概念周围，开发操作系统。</p>
<p>　　有缘千里来相会，无缘对面不相识。Steve Jobs 和 Cordell Ratzlaff 算是相见恨晚。这样由 Cordell Ratzlaff 主导的新界面，在 Steve Jobs 的支持下，横扫一切困难，成为新版操作系统界面的最大亮点。</p>
<p>　　从这时到 Steve Jobs 正式在舞台上秀他的 Mac OS X Public Beta，还有 18 个月。此时，系统界面革命的旅程已经开始，一道神秘的天光射向 Infinity Loop，千古杰作 Aqua 就要在这里诞生，其光辉历程，我们下篇再谈。</p>
<h1 id="Mac-OS-X-背后的故事（七）上善若水下——Cordell-Ratzlaff-引发的-Aqua-革命"><a href="#Mac-OS-X-背后的故事（七）上善若水下——Cordell-Ratzlaff-引发的-Aqua-革命" class="headerlink" title="Mac OS X 背后的故事（七）上善若水下——Cordell Ratzlaff 引发的 Aqua 革命"></a>Mac OS X 背后的故事（七）上善若水下——Cordell Ratzlaff 引发的 Aqua 革命</h1><p>　　在前一节中讲到，Cordell Ratzlaff 新界面方案得到 Steve Jobs 的高度肯定，Steve Jobs 让各开发组紧紧围绕在界面设计组周围，共同建造 Mac OS X。此时，离 Mac OS X 第一个公共测试版的发布，仅有一年半时间。这时苹果的设计构想，还仅仅是个概念，在本篇中我们将展示 Aqua 的具体设计过程。</p>
<h2 id="设计与软件的融合"><a href="#设计与软件的融合" class="headerlink" title="设计与软件的融合"></a>设计与软件的融合</h2><p>　　开发分设计和软件两条路并行走，“两手抓，两手都要硬”。</p>
<p>设计是个有趣的领域。有些人认为，设计就是产品的外观看上去什么样。但其实，如果细想一下，你会发现设计其实是有关产品如何工作的学问。<br>　　——Steve Jobs</p>
<p>　　首先，苹果定下计划，并规划整个界面设计元素的方案，把设想通过可操作性强的材料让工程师来实现。<br>　　Cordell Ratzlaff 每周都要和 Steve Jobs 开会，向他展示界面设计小组最新成果。任何大家现在见到的各界面控件，如菜单、按钮、进度条、Steve Jobs 都一一过目，毫不马虎。针对每一个控件，Cordell Ratzlaff 会要求拿出多套方案来，让 Steve Jobs 选出他中意的。Steve Jobs 也会提出各种他自己的见解和改进建议，而 Cordell Ratzlaff 则会根据这些回馈不断修改，直到 Steve Jobs 满意为止。</p>
<p>　　与此同时，软件工程师也以越来越重的比例加入到这个设计行列中。</p>
<p>　　图形界面设计小组使用的设计软件是 Macromedia Director。它能做出演示用的动画，可以演示打开、关闭窗口、下拉菜单等模拟效果，但这些并不是可供用户使用的最终软件。软件工程师需要把图形界面设计师的设计，变为一行行代码，运用到 Mac OS X 中。所以每次会议的 Macromedia Director 动画演示机旁，还会有一台计算机，预装了软件工程师转换的代码。当工程师们向 Steve Jobs 展示最新代码如何工作时，Steve Jobs 会身体前倾，鼻子快贴到荧幕上，观察细微到“像素级别”来比较软件的表现和之前的设计是否完全一致。如果他有发现任何细微的差错，一阵类似“你们全是一帮白痴”的腥风血雨就会在办公室中展开。</p>
<p>　　设计整套方案是一个令人难以置信的漫长过程，尤其是遇到追求完美的 Steve Jobs。Mac OS X 中有一个控件叫滚动条（NSScroller）。当需要显示的内容长于当前控件大小时就会出现滚动条，可上下翻阅内容。这是一个非常不起眼的控件，大多数时间，用户甚至注意不到它的存在，甚至在十年后的今天它都被默认不显示了（关于 Lion 图形界面的改动受 iOS 思潮的影响我们今后会提到）。但哪怕是这种不起眼的细节，Steve Jobs 都偏执地当个大项目来做。Mac OS X 的界面设计是有史以来最复杂的一个，需要考虑诸多因素——比如所在窗口的活动与否，都会影响这个控件的颜色等属性。就滚动条而言，箭头的大小、位置的变化、颜色的启用等全都是活动的属性，牵一发而动全身。一根看似简单得不能再简单的滚动条，设计组花了整整六个月来修改。</p>
<p>　　当时，Mac OS X 的用户界面有两个重大的设计目标：第一是让老用户没有压力地迁移过来，且倍感新界面的好用；第二是让那些从未摸过 Mac 的人尽快上手，并称赞这界面很好很强大。所以，整个界面设计保留了老 Mac OS 界面元素的设计理念，但同时又对很多有问题的老设计进行了革新。比如，在老版 Mac OS 中，各种系统设置选项是隐藏在不计其数的系统扩展、控制面板，以及很多系统组件中的。用户要想联个网，要去五六个地方设网络、设 IP、设连接设密码，而在 Mac OS X 中，所有这些设置都被分门别类地规类到一个单一的程序——系统首选项（System Preferences），让用户“足不出户”，就能进行一切相关设置。</p>
<h2 id="精简的狂热追求和大胆的设计创新"><a href="#精简的狂热追求和大胆的设计创新" class="headerlink" title="精简的狂热追求和大胆的设计创新"></a>精简的狂热追求和大胆的设计创新</h2><p>　　Apple 偏爱最简化的设计，而往往满屏的窗口让 Steve Jobs 忍无可忍。又酷又炫的 Dock 横空出世，巧妙地解决了这个问题。Dock 的设计源于 Mac OS X 的前身 NeXTSTEP，但在 Mac OS X 中完全被重写，并重定义了它的功能。Dock 提供用户一个放置常用软件图标、闲置窗口、文档的场所，Steve Jobs 说“任何东西都能被拉进 Dock”。但 Dock 真正神奇的，是它犹如多拉A梦的口袋，有无限的承载能力。当放入 Dock 中的东西变多时，它会自动把横向宽度变长、图标变小，可承载几十个窗口。当窗口缩入和还原时，都配有“精灵”一样的动画——在 Dock 的图标多的时候，每个图标很小，用户就很难找到需要的——灵动且放大动画可以让用户能快速地找到所需。</p>
<p>　　另外，起初版本的 Dock 中每个图标都是正方形的方块，被换成半透明的背景，看得人垂涎欲滴。这些经典的设计，影响了整整一代图形界面设计者，被各山寨界面抄了一遍又一遍，甚至又活在当今的 Ubuntu Linux 的 Unity 和 Windows 7 中。</p>
<p>　　Apple 追求清爽甚至到了发疯的地步，在最初版的 Mac OS X Public Beta 中，每个窗口有一个按钮，只要按下，除了当前窗口外，其它一切都会飞入 Dock。因此，只要一键，“整个世界都清静了”。而在后来每个版本的 Mac OS X 中，都有大的更新来防止窗口或其他界面元素的堆积。10. 4 时代的 Expose，10. 5 时代的 Stack 和 Spaces，10.6 时代的 Expose 和 Dock 相结合双管齐下，到 10.7 时代的 Mission Control，都是用来解决果面精简这一个问题的。</p>
<p>　　而很多传统的界面控件也被赋予了新的含义。比如 Steve Jobs 觉得，“最大化”一个窗口没有实际意义，而且把整个窗口最大化，也会挡住后面的窗口（直到 2011 年，Apple 用“全屏”来重新定义传统的“最大化”）。而 Mac OS X 没有所谓的“最大化”，取而代之的是自动计算后调整窗口到所需大小的“最适化按钮”。而关闭一个窗口的含意也不该是关闭一个程序，而只应是结束目前的内容。Apple 的许多设计都格外具有魄力，完全重写了界面设计的教科书。当然，有许多地方 Apple 确实做得矫枉过正，比如 Apple 一直是我见过的只有拖住右下角才能改动窗口大小的唯一系统。这个置用户于不顾的狂妄设计，一直在十年后发布的 Lion 中，才得以改变。</p>
<p>　　Steve Jobs 一直是界面设计的重要顾问。他有时候会提出一些看似稀奇古怪的意见，但往往最终又被证明是好的。比如，有一次他在会上指出，窗口左上角的“关闭”、“最小化”、“最适化”三个按钮的颜色都是一样的灰色，不容易区分他们。他建议把三个按钮变成交通灯的颜色，并且当鼠标移到附近时，显示出相应的图形指示。当 Cordell Ratzlaff 一群人听到这个主意后面色大变，认为简直是计算机图形设计史上最好笑的段子——谁会把电脑当交通灯使啊。不过改完后，他们对 Steve Jobs 心悦诚服——“红灯给用户一个终止的警示，这个窗口要被关掉；黄灯表示这个窗口要被放入等待队列，以便以后再通行；最适化则是给这个窗口大开绿灯”——这样高明的比喻，使 Cordell Ratzlaff 对 Steve Jobs 崇拜得五体投地。</p>
<p>　　18个月转瞬即逝，“你们就是一群白痴”的骂声依旧清晰，而此时的 Mac OS X 的图形界面，已今非昔彼。</p>
<p>“语静声息。我走上舞台。依着那打开的门，我试图探测回声中，蕴涵着什么样的未来。”（北岛翻译的帕斯捷尔纳克的《哈姆雷特》）。</p>
<p>　　18 个月后的 2000 年 1 月，新世纪的钟声刚刚敲响，Steve Jobs 镇定地走上 MacWorld 大会的舞台，独领风骚的新世纪的经典大作 Aqua，此时，就要被他揭开帷幕。</p>
<h1 id="Mac-OS-X-背后的故事（八）三好学生-Chris-Lattner-的-LLVM-编译工具链"><a href="#Mac-OS-X-背后的故事（八）三好学生-Chris-Lattner-的-LLVM-编译工具链" class="headerlink" title="Mac OS X 背后的故事（八）三好学生 Chris Lattner 的 LLVM 编译工具链"></a>Mac OS X 背后的故事（八）三好学生 Chris Lattner 的 LLVM 编译工具链</h1><p>　　2011年 12 月 3 日，LLVM 3.0 正式版发布，完整支持所有 ISO C++ 标准和大部分 C++ 0x 的新特性， 这对于一个短短几年的全新项目来说非常不易。</p>
<h2 id="开发者的惊愕"><a href="#开发者的惊愕" class="headerlink" title="开发者的惊愕"></a>开发者的惊愕</h2><p>　　在 2011 年 WWDC（苹果全球开发者大会）的一场与 Objective-C 相关的讲座上，开发者的人生观被颠覆了。</p>
<p>　　作为一个开发者，管理好自己程序所使用的内存是天经地义的事，好比人们在溜狗时必须清理狗的排泄物一样（美国随处可见“Clean up after your dogs”的标志）。在本科阶段上 C 语言的课程时，教授们会向学生反复强调：如果使用 malloc 函数申请了一块内存，使用完后必须再使用 free 函数把申请的内存还给系统——如果不还，会造成“内存泄漏”的结果。这对于 Hello World 可能还不算严重，但对于庞大的程序或是长时间运行的服务器程序，泄内存是致命的。如果没记住，自己还清理了两次，造成的结果则严重得多——直接导致程序崩溃。</p>
<p>　　Objective-C 有类似 malloc/free 的对子，叫 alloc/dealloc，这种原始的方式如同管理C内存一样困难。所以 Objective-C 中的内存管理又增加了“引用计数”的方法，也就是如果一个物件被别的物件引用一次，则引用计数加一；如果不再被该物件引用，则引用计数减一；当引用计数减至零时，则系统自动清掉该物件所占的内存。具体来说，如果我们有一个字符串，当建立时，需要使用 alloc 方法来申请内存，引用计数则变成了一；然后被其他物件引用时，需要用 retain 方法去增加它的引用计数，变成二。当它和刚才引用的物件脱离关联时，需使 release 方法减少引用计数，又变回了一；最后，使用完这个字符串时，再用 release 方法减少其引用计数，这时，运行库发现其引用计数变为零了，则回收走它的内存。这是手动的方式。</p>
<p>　　这种方式自然很麻烦，所以又设计出一种叫做 autorelease 的机制（不是类似 Java 的自动垃圾回收）。在 Objective-C 中，设计了一个叫做 NSAutoReleasePool 的池，当开发者需要完成一个任务时（比如每开启一个线程，或者开始一个函数），可以手动创立一个这样的池子， 然后通过显式声明把物件扔进自动回收池中。NSAutoReleasePool 内有一个数组来保存声明为 autorelease 的所有对象。如果一个对象声明为 autorelease，则会自动加到池子里。如果完成了一个任务（结束线程了，或者退出那个函数），则开发者需对这个池子发送一个 drain 消息。这时，NSAutoReleasePool 会对池子中所有的物件发送 release 消息，把它们的引用计数都减一 ——这就好比游泳池关门时通知所有客人都“滚蛋”一样。所以开发者无需显式声明 release，所有的物件也会在池子清空时自动呼叫 release 函数，如果引用计数变成零了，系统才回收那块内存。所以这是个半自动、半手动的方式。</p>
<p>　　Objective-C 的这种方式虽然比起 C 来进了一大步，我刚才花了几分钟就和读者讲明白了。只要遵守上面这两个简单的规则，就可以保证不犯任何错误。但这和后来的 Java 自动垃圾回收相比则是非常繁琐的，哪怕是再熟练的开发者，一不小心就会弄错。而且，哪怕很简单的代码，比如物件的 getter/setter 函数，都需要用户写上一堆的代码来管理接收来的物件的内存。</p>
<p>　　经典教材《Cocoa Programming for Mac OS X》用了整整一章节的篇幅，来讲解 Objective-C 中内存管理相关的内容，但初学者们看得还是一头雾水。所以，在 2007 年 10.5 发布时，Objective-C 做出了有史以来最大的更新，最大的亮点是它的运行库 libobjc 2.0 正式支持自动垃圾回收，也就是由运行库在运行时随时侦测哪些物件需要被释放。听上去很不错，可惜使用这个技术的项目却少之又少。原因很简单，使用这个特性，会有很大的性能损失，使 Objective-C 的内存管理效率低得和 Java 一样，而且一旦有一个模块启用了这个特性，这个进程中所有的地方都要启用这个特性——因此如果你写了一个使用垃圾回收的库，那所有引用你库的程序就都得被迫使用垃圾回收。所以 Apple 自己也不使用这项技术，大量的第三方库也不使用它。</p>
<p>　　这个问题随 Apple 在移动市场的一炮走红而变得更加严峻。不过这次，Apple 和与会的开发者讲，他们找到了一个解决问题的终极方法，这个方法把从世界各地专程赶来聆听圣谕的开发者惊得目瞪口呆——你不用写任何内存管理代码，也不需要使用自动垃圾回收。因为我们的编译器已经学会了上面所介绍的内存管理规则，会自动在编译程序时把这些代码插进去。</p>
<p>　　这个编译器，一直是 Apple 公开的秘密——LLVM。说它公开，是因为它自始至终都是一个开源项目；而秘密，则是因为它从来没公开在 WWDC 的 Keynote 演讲上亮相过 。<br>　　一直关注这系列连载的读者一定还记得，在第二篇《Linus Torvalds 的短视》介绍 Apple 和 GPL 社区的不合时，提到过“自以为是但代码又写得差的开源项目，Apple 事后也遇到不少，比如 GCC 编译器项目组。虽然大把钞票扔进去，在先期能够解决一些问题，但时间长了这群人总和 Apple 过不去，并以自己在开源世界的地位恫吓之，最终 Apple 由于受不了这些项目组的态度、协议、代码质量，觉得还不如自己造轮子来得方便。”LLVM 则是 Apple 造的这个轮子，它的目的是完全替代掉 GCC 那条编译链。它的主要作者，则是现在就职于 Apple 的 Chris Lattner。</p>
<h2 id="编译器高材生-Chris-Lattner"><a href="#编译器高材生-Chris-Lattner" class="headerlink" title="编译器高材生 Chris Lattner"></a>编译器高材生 Chris Lattner</h2><p>　　2000年，本科毕业的 Chris Lattner 像中国多数大学生一样，按部就班地考了 GRE，最终前往 UIUC（伊利诺伊大学厄巴纳香槟分校），开始了艰苦读计算机硕士和博士的生涯。在这阶段，他不仅周游美国各大景点，更是努力学习科学文化知识，翻烂了“龙书”（《Compilers: Principles, Techniques, and Tools》），成了 GPA 牛人【注：最终学分积 4.0 满分】，以及不断地研究探索关于编译器的未知领域，发表了一篇又一篇的论文，是中国传统观念里的“三好学生”。他的硕士毕业论文提出了一套完整的在编译时、链接时、运行时甚至是在闲置时优化程序的编译思想，直接奠定了 LLVM 的基础。</p>
<!--more-->
<p>　　LLVM 在他念博士时更加成熟，使用 GCC 作为前端来对用户程序进行语义分析产生 IF（Intermidiate Format），然后 LLVM 使用分析结果完成代码优化和生成。这项研究让他在 2005 年毕业时，成为小有名气的编译器专家，他也因此早早地被 Apple 相中，成为其编译器项目的骨干。<br>　　Apple 相中 Chris Lattner 主要是看中 LLVM 能摆脱 GCC 束缚。Apple（包括中后期的 NeXT） 一直使用 GCC 作为官方的编译器。GCC 作为开源世界的编译器标准一直做得不错，但 Apple 对编译工具会提出更高的要求。</p>
<p>　　一方面，是 Apple 对 Objective-C 语言（甚至后来对 C 语言）新增很多特性，但 GCC 开发者并不买 Apple 的帐——不给实现，因此索性后来两者分成两条分支分别开发，这也造成 Apple 的编译器版本远落后于 GCC 的官方版本。另一方面，GCC 的代码耦合度太高，不好独立，而且越是后期的版本，代码质量越差，但 Apple 想做的很多功能（比如更好的 IDE 支持）需要模块化的方式来调用 GCC，但 GCC 一直不给做。甚至最近，《GCC 运行环境豁免条款 （英文版）》从根本上限制了 LLVM-GCC 的开发。 所以，这种不和让 Apple 一直在寻找一个高效的、模块化的、协议更放松的开源替代品，Chris Lattner 的 LLVM 显然是一个很棒的选择。</p>
<p>　　刚进入 Apple，Chris Lattner 就大展身手：首先在 OpenGL 小组做代码优化，把 LLVM 运行时的编译架在 OpenGL 栈上，这样 OpenGL 栈能够产出更高效率的图形代码。如果显卡足够高级，这些代码会直接扔入 GPU 执行。但对于一些不支持全部 OpenGL 特性的显卡（比如当时的 Intel GMA 卡），LLVM 则能够把这些指令优化成高效的 CPU 指令，使程序依然能够正常运行。这个强大的 OpenGL 实现被用在了后来发布的 Mac OS X 10.5 上。同时，LLVM 的链接优化被直接加入到 Apple 的代码链接器上，而 LLVM-GCC 也被同步到使用 GCC 4 代码。</p>
<p>　　LLVM 真正的发迹，则得等到 Mac OS X 10.6 Snow Leopard 登上舞台。可以说， Snow Leopard 的新功能，完全得益于 LLVM 的技术。而这一个版本，也是将 LLVM 推向真正成熟的重大机遇。</p>
<p>　　关于 Snow Leopard 的三项主推技术（64位支持、OpenCL，以及 Grand Central Dispatch）的细节，我们会在下一次有整整一期篇幅仔细讨论，这次只是点到为止——我们告诉读者，这些技术，不但需要语言层面的支持（比如 Grand Centrual Dispatch 所用到的“代码块”语法， 这被很多人看作是带 lambda 的 C），也需要底层代码生成和优化（比如 OpenCL 是在运行时编译为 GPU 或 CPU 代码并发执行的）。而这些需求得以实现，归功于 LLVM 自身的新前端——Clang。</p>
<h2 id="优异的答卷——Clang"><a href="#优异的答卷——Clang" class="headerlink" title="优异的答卷——Clang"></a>优异的答卷——Clang</h2><p>　　前文提到，Apple 吸收 Chris Lattner 的目的要比改进 GCC 代码优化宏大得多——GCC 系统庞大而笨重，而 Apple 大量使用的 Objective-C 在 GCC 中优先级很低。此外 GCC 作为一个纯粹的编译系统，与 IDE 配合得很差。加之许可证方面的要求，Apple 无法使用 LLVM 继续改进 GCC 的代码质量。于是，Apple 决定从零开始写 C、C++、Objective-C 语言的前端 Clang，完全替代掉 GCC。</p>
<p>　　正像名字所写的那样，Clang 只支持 C，C++和 Objective-C 三种C家族语言。2007年开始开发，C 编译器最早完成，而由于 Objective-C 相对简单，只是 C 语言的一个简单扩展，很多情况下甚至可以等价地改写为C语言对 Objective-C 运行库的函数调用，因此在 2009 年时，已经完全可以用于生产环境。C++ 的支持也热火朝天地进行着。</p>
<p>　　Clang 的加入代表着 LLVM 真正走向成熟和全能，Chris Lattner 以影响他最大的“龙书”封面【注：见 <a href="http://en.wikipedia.org/wiki/Dragon_Book" target="_blank" rel="noopener">http://en.wikipedia.org/wiki/Dragon_Book</a>_ (computer_science)】为灵感，为项目选定了图标——一条张牙舞爪的飞龙。</p>
<p>　　Clang 一个重要的特性是编译快速，占内存少，而代码质量还比 GCC 来得高。测试结果表明 Clang 编译 Objective-C 代码时速度为 GCC 的 3 倍【注：<a href="http://llvm.org/pubs/2007-07-25-LLVM-2.0-and-Beyond.pdf" target="_blank" rel="noopener">http://llvm.org/pubs/2007-07-25-LLVM-2.0-and-Beyond.pdf</a> 】，而语法树（AST）内存占用则为被编译源码的 1.3 倍，而 GCC 则可以轻易地可以超过 10 倍。Clang 不但编译代码快，对于用户犯下的错误，也能够更准确地给出建议。使用过 GCC 的读者应该熟悉，GCC 给出的错误提示基本都不是给人看的。比如最简单的：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">foo</span> &#123;</span> <span class="keyword">int</span> x; &#125;</span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">int</span> bar;</span><br></pre></td></tr></table></figure>
<p>如果使用 GCC 编译，它将告诉你：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">t.c:3: error: two or more data types <span class="keyword">in</span> declaration specifiers</span><br></pre></td></tr></table></figure>
<p>但是 Clang 给出的出错提示则显得人性化得多：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">t.c:1:22: error: expected ‘;’ after struct</span><br></pre></td></tr></table></figure>
<p>甚至，Clang 可以根据语境，像拼写检查程序一样地告诉你可能的替代方案。比如这个程序：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;inttypes.h&gt;</span></span></span><br><span class="line">int64 x;</span><br></pre></td></tr></table></figure>
<p>GCC 一样给出乱码似的出错提示：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">t.c:2: error: expected ‘=’， ‘，’， ‘;’， ‘asm’ or ‘__attribute__’ before ‘x’</span><br></pre></td></tr></table></figure>
<p>而优雅的 Clang 则用彩色的提示告诉你是不是拼错了，并给出可能的变量名：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">t.c:2:1: error: unknown <span class="built_in">type</span> name ‘int64′; did you mean ‘int64_t’?</span><br><span class="line">int64 x;^~~~~int64_t</span><br></pre></td></tr></table></figure>
<p>更多的例子可以参考<a href="http://blog.llvm.org/2010/04/amazing-feats-of-clang-error-recovery.html" target="_blank" rel="noopener">http://blog.llvm.org/2010/04/amazing-feats-of-clang-error-recovery.html</a> 。 而同时又因为 Clang 是高度模块化的一个前端，很容易实现代码的高度重用。所以比如 Xcode 4.0 的集成编程环境就使用 Clang 的模块来实现代码的自动加亮、代码出错的提示和自动的代码补全。开发者使用 Xcode 4.0 以后的版本，可以极大地提高编程效率，尽可能地降低编译错误的发生率。</p>
<p>　　支持 C++ 也是 Clang 的一项重要使命。C++ 是一门非常复杂的语言，大多编译器（如 GCC、MSVC）用了十多年甚至二十多年来完善对 C++ 的支持，但效果依然不很理想。Clang 的 C++ 支持却一直如火如荼地展开着。2010 年 2 月 4 日，Clang 已经成熟到能自举（即使用 Clang 编译 Clang，到我发稿时，LLVM 3.0 发布已完整支持所有 ISO C++ 标准，以及大部分C++ 0x 的新特性。</p>
<p>　　这对于一个短短几年的全新项目来说是非常不易的。得益于本身健壮的架构和 Apple 的大力支持，Clang 越来越全能，从 FreeBSD 到 Linux Kernel ， 从 Boost 到 Java 虚拟机， Clang 支持的项目越来越多。</p>
<p>　　Apple 的 Mac OS X 以及 iOS 也成了 Clang 和 LLVM 的主要试验场——10.6 时代，很多需要高效运行的程序比如 OpenSSL 和 Hotspot 就由 LLVM-GCC 编译来加速的。而 10.6 时代的 Xcode 3.2 诸多图形界面开发程序如 Xcode、Interface Builder 等，皆由 Clang 编译。到了 Mac OS X 10.7，整个系统的的代码都由 Clang 或 LLVM-GCC 编译【注：<a href="http://llvm.org/Users.html" target="_blank" rel="noopener">http://llvm.org/Users.html</a> 】。<br>LLVM 周边工具</p>
<p>　　由于受到 Clang 项目的威胁，GCC 也不得不软下来，让自己变得稍微模块化一些，推出插件的支持，而 LLVM 项目则顺水推舟，索性废掉了出道时就一直作为看家本领的 LLVM-GCC，改为一个 GCC 的插件 DragonEgg。 Apple 也于 Xcode 4.2 彻底抛弃了 GCC 工具链。</p>
<p>　　而 Clang 的一个重要衍生项目，则是静态分析工具，能够通过自动分折程序的逻辑，在编译时就找出程序可能的 bug。在 Mac OS X 10.6 时，静态分析被集成进 Xcode 3.2，帮助用户查找自己犯下的错误。其中一个功能，就是告诉用户内存管理的 Bug，比如 alloc 了一个物件却忘记使用 release 回收。这已经是一项很可怕的技术，而 Apple 自己一定使用它来发现并改正 Mac OS X 整个系统各层面的问题。但许多开发者还不满足——既然你能发现我漏写了 release，你为什么不能帮我自动加上呢？于是 ARC 被集成进 Clang，发生了文章开头开发者们的惊愕——从来没有人觉得这件事是可以做成的。</p>
<p>　　除 LLVM 核心和 Clang 以外，LLVM 还包括一些重要的子项目，比如一个原生支持调试多线程程序的调试器 LLDB，和一个 C++ 的标准库 libstdc++，这些项目由于是从零重写的，因此要比先前的很多项目站得更高，比如先前 GNU、Apache、STLport 等 C++ 标准库在设计时，C++0x 标准还未公布，所以大多不支持这些新标准或者需要通过一些肮脏的改动才能支持，而 libstdc++ 则原生支持C++0x。而且在现代架构上，这些项目能动用多核把事情处理得更好。</p>
<p>　　不单单是 Apple，诸多的项目和编程语言都从 LLVM 里取得了关键性的技术。Haskell 语言编译器 GHC 使用 LLVM 作为后端，实现了高质量的代码编译。很多动态语言实现也使用 LLVM 作为运行时的编译工具，较著名的有 Google 的 Unladen Swallow【注：Python 实现，后夭折】、PyPy【注：Python 实现】，以及 MacRuby【注：Ruby 实现】。例如 MacRuby 后端改为 LLVM 后，速度不但有了显著的提高，更是支持 Grand Central Dispatch 来实现高度的并行运行。由于 LLVM 高度的模块化，很方便重用其中的组件来作为一个实现的重要组成部分，因此类似的项目会越来越多。</p>
<p>　　LLVM 的成熟也给其他痛恨 GCC 的开发项目出了一口恶气。其中最重要的，恐怕是以 FreeBSD 为代表的 BSD 社区。BSD 社区和 Apple 的联系一向很紧密，而且由于代码相似，很多 Apple 的技术如 Grand Central Dispatch 也是最早移植到 FreeBSD 上。BSD 社区很早就在找 GCC 的替代品，无奈大多都很差（如 Portable C Compiler 产生的代码质量和 GCC 不能同日而语）。</p>
<p>　　一方面是因为不满意 GCC 的代码品质【注：BSD 代码整体要比 GNU 的高一些，GNU 代码永无休止地出现各种严重的安全问题】，更重要的是协议问题。BSD 开发者有洁癖的居多，大多都不喜欢 GPL 代码，尤其是 GPL 协议第三版发布时，和 FreeBSD 的协议甚至是冲突的。这也正是为什么 FreeBSD 中包含的 GNU 的 C++ 运行库还是 2007 年以 GPLv2 发布的老版本，而不是支持C++0x 的但依 GPLv3 协议发布的新版本。因此历时两年的开发后，2012年初发布的 FreeBSD 9.0 中，Clang 被加入到 FreeBSD 的基础系统。 但这只是第一步，因为 FreeBSD 中依然使用 GNU 的 C++ STL 库、C++ 运行库、GDB 调试器、libgcc/libgcc_s 编译库都是和编译相关的重要底层技术，先前全被 GNU 垄断，而现在 LLVM 子项目 lldb、libstdc++、compiler-rt 等项目的出现，使 BSD 社区有机会向 GNU 说“不”，因此一个把 GNU 组件移出 FreeBSD 的计划被构想出来，并完成了很大一部分。编写过《Cocoa Programming Developer’s Handbook》的著名 Objective-C 牛人 David Chisnall 也被吸收入 FreeBSD 开发组完成这个计划的关键部分。 预计在 FreeBSD 10 发布时，将不再包含 GNU 代码。</p>
<p>　　LLVM 在短短五年内取得的快速发展充分反映了 Apple 对于产品技术的远见和处理争端的决心和手腕，并一跃成为最领先的开源软件技术。而 Chris Lattner 在 2010 年也赢得了他应有的荣誉——Programming Languages Software Award（程序设计语言软件奖）。</p>

      
    </div>

    
      

  <div class="popular-posts-header">相关文章</div>
  <ul class="popular-posts">
  
    <li class="popular-posts-item">
      
      
      <div class="popular-posts-title"><a href="/macos/README.html" rel="bookmark">macOS 使用简介</a></div>
      
    </li>
  
    <li class="popular-posts-item">
      
      
      <div class="popular-posts-title"><a href="/macos/brew.html" rel="bookmark">brew 安装配置</a></div>
      
    </li>
  
    <li class="popular-posts-item">
      
      
      <div class="popular-posts-title"><a href="/macos/gdb.html" rel="bookmark">macOS 对 gdb 进行代码签名</a></div>
      
    </li>
  
    <li class="popular-posts-item">
      
      
      <div class="popular-posts-title"><a href="/macos/story3.html" rel="bookmark">Mac OS X 背后的故事（补充）</a></div>
      
    </li>
  
    <li class="popular-posts-item">
      
      
      <div class="popular-posts-title"><a href="/macos/story2.html" rel="bookmark">Mac OS X 背后的故事（下）</a></div>
      
    </li>
  
  </ul>


    

    
    
    

    

    
      
    
    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/macOS/" rel="tag"># macOS</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/macos/story2.html" rel="next" title="Mac OS X 背后的故事（下）">
                <i class="fa fa-chevron-left"></i> Mac OS X 背后的故事（下）
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/macos/brew.html" rel="prev" title="brew 安装配置">
                brew 安装配置 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>


  </div>


          </div>
          

  
    <div class="comments" id="comments">
    </div>

  



        </div>
        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">  <a href="http://www.miitbeian.gov.cn" rel="noopener" target="_blank">晋ICP备16007512号 </a>&copy; <span itemprop="copyrightYear">2019</span>
  <span class="with-love" id="animate">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">khs1994</span>

  

  
</div>


  <div class="powered-by">由 <a href="https://hexo.io" class="theme-link" rel="noopener" target="_blank">Hexo</a> 强力驱动 v3.8.0</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 – <a href="https://theme-next.org" class="theme-link" rel="noopener" target="_blank">NexT.Mist</a> v7.0.1</div>



  <div class="footer-custom">LNMP by <a href="https://github.com/khs1994-docker/lnmp" class="theme-link" rel="noopener" target="_blank">khs1994-docker/lnmp</a></div>


        


  <script>
    var _mtac = {};
    (function() {
      var mta = document.createElement("script");
      mta.src = "https://pingjs.qq.com/h5/stats.js";
      mta.setAttribute("name", "MTAH5");
      mta.setAttribute("sid", "500439913");
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(mta, s);
    })();
  </script>







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
          <span id="scrollpercent"><span>0</span>%</span>
        
      </div>
    

    

    

    
  </div>

  

<script>
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>
























  



  
  <script src="/lib/jquery/index.js?v=2.1.3"></script>

  
  <script src="/lib/velocity/velocity.min.js?v=1.2.1"></script>

  
  <script src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>

  
  <script src="/lib/reading_progress/reading_progress.js"></script>


  


  <script src="/js/src/utils.js?v=7.0.1"></script>

  <script src="/js/src/motion.js?v=7.0.1"></script>



  
  


  <script src="/js/src/schemes/muse.js?v=7.0.1"></script>




  
  <script src="/js/src/scrollspy.js?v=7.0.1"></script>
<script src="/js/src/post-details.js?v=7.0.1"></script>



  


  <script src="/js/src/next-boot.js?v=7.0.1"></script>


  

  

  

  
  

<script src="//cdn1.lncld.net/static/js/3.11.1/av-min.js"></script>



<script src="//unpkg.com/valine/dist/Valine.min.js"></script>

<script>
  var GUEST = ['nick', 'mail', 'link'];
  var guest = 'nick,mail,link';
  guest = guest.split(',').filter(function(item) {
    return GUEST.indexOf(item) > -1;
  });
  new Valine({
    el: '#comments',
    verify: false,
    notify: true,
    appId: 'XIHxBRQs1ijkKrdjHvb5DJa7-gzGzoHsz',
    appKey: 'GtQLQEn98uwVGhni3IhveGkc',
    placeholder: 'Just go go',
    avatar: 'mm',
    meta: guest,
    pageSize: '10' || 10,
    visitor: true,
    lang: '' || 'zh-cn'
  });
</script>




  


  
  <script>
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url).replace(/\/{2,}/g, '/');
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x"></i></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x"></i></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  

  

  

  

  
  <script>
    (function(){
      var bp = document.createElement('script');
      var curProtocol = window.location.protocol.split(':')[0];
      bp.src = (curProtocol === 'https') ? 'https://zz.bdstatic.com/linksubmit/push.js' : 'http://push.zhanzhang.baidu.com/push.js';
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(bp, s);
    })();
  </script>


  

  

  

  

  
  
  
  <script src="/lib/bookmark/bookmark.min.js?v=1.0"></script>
  <script>
  
    bookmark.scrollToMark('auto', "#更多");
  
  </script>


  
<script>
  $('.highlight').each(function(i, e) {
    var $wrap = $('<div>').addClass('highlight-wrap');
    $(e).after($wrap);
    $wrap.append($('<button>').addClass('copy-btn').append('复制').on('click', function(e) {
      var code = $(this).parent().find('.code').find('.line').map(function(i, e) {
        return $(e).text();
      }).toArray().join('\n');
      var ta = document.createElement('textarea');
      var yPosition = window.pageYOffset || document.documentElement.scrollTop;
      ta.style.top = yPosition + 'px'; // Prevent page scroll
      ta.style.position = 'absolute';
      ta.style.opacity = '0';
      ta.readOnly = true;
      ta.value = code;
      document.body.appendChild(ta);
      ta.select();
      ta.setSelectionRange(0, code.length);
      ta.readOnly = false;
      var result = document.execCommand('copy');
      
        if (result) $(this).text('复制成功');
        else $(this).text('复制失败');
      
      ta.blur(); // For iOS
      $(this).blur();
    })).on('mouseleave', function(e) {
      var $b = $(this).find('.copy-btn');
      setTimeout(function() {
        $b.text('复制');
      }, 300);
    }).append(e);
  })
</script>


  

  

</body>
</html>
